Code Monkey home page Code Monkey logo

blog's Introduction

blog's People

Contributors

ououpao avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar

blog's Issues

AngularJS 最佳实践

最近在整理公司的前端规范,顺便也整理一份angular的最佳实践

项目结构

  • 标准结构(适用小型项目)
app/
----- controllers/
---------- mainController.js
---------- otherController.js
----- directives/
---------- mainDirective.js
---------- otherDirective.js
----- services/
---------- userService.js
---------- itemService.js
----- js/
---------- bootstrap.js
---------- jquery.js
----- app.js
views/
----- mainView.html
----- otherView.html
----- index.html    

这是比较经典的项目结构,以类型分类,好处就是一看目录就知道里面是什么文件,当文件不多的时候也是容易查找阅读。但是,当每一个分类的文件都比较多的时候,不仅混乱难查找,也更难维护,理解修改成本较大。

  • 更好的结构(适用中大型项目)
app/
----- shared/   // acts as reusable components or partials of our site
---------- sidebar/
--------------- sidebarDirective.js
--------------- sidebarView.html
---------- article/
--------------- articleDirective.js
--------------- articleView.html
----- components/   // each component is treated as a mini Angular app
---------- home/
--------------- homeController.js
--------------- homeService.js
--------------- homeView.html
---------- blog/
--------------- blogController.js
--------------- blogService.js
--------------- blogView.html
----- app.module.js
----- app.routes.js
assets/
----- img/      // Images and icons for your app
----- css/      // All styles and style related files (SCSS or LESS files)
----- js/       // JavaScript files written for your app that are not for angular
----- libs/     // Third-party libraries such as jQuery, Moment, Underscore, etc.
index.html

这是以模块划分,组件化的概念的项目结构。同一个功能模块的相关文件都在一个文件夹内,一幕了然,职责清晰。这种结构可能对新手来说难理解一点,但随着对项目的熟悉,这种结构也会带来更大的工作效率。

最佳实践、规范

请阅读:Angular规范

优化

  • 减少监控值的个数

    由于在html模板中绑定的数据,angular都会为其创建相应的监听器,绑定的数据越多,监听器越多,那么angular在进行脏值检测的时候遍历的监听器也就越多,性能就会降低。所以当我们的页面渲染完成后不需要更新。则采用单次绑定,仅在初始化的时候绑定:
    单个值:

    <div>{{::value}}</div>

    列表:

    <div ng-repeat="item in ::list"></div>
  • 进行数据绑定时,使用对象字面量或者使用controllerAS的方式代替直接往$scope中添加属性。

    使用
    <input type="text" ng-model="obj.prop">
    来替代
    <input type="text" ng-model="obj.prop">
  • 使用track by 提升索引的性能

    在angular中,在数据变更时DOM也会相应的更新,所以,DOM和数据需要建立关联,即需要一个映射关系,映射关系需要唯一的索引,angular默认对简单类型使用自身当索引,当出现重复的时候,就会出错了。如果指定$index,也就是元素在数组中的下标为索引,就可以避免这个问题。
    简单数组:

    this.arr = [1, 3, 5, 3];
    <!-- 报错 -->
    <ul>
        <li ng-repeat="num in arr">{{num}}</li>
    </ul
    <!-- 指定$index为索引 -->
    <ul>
        <li ng-repeat="num in arr track by $index">{{num}}</li>
    </ul

    对象数组:

    this.arr = [{
        id: 1,
        name: 'naraku666'
    }, {
        id: 2,
        name: 'ljs'
    }, ...];
    <!-- 性能较差 -->
    <ul>
        <li ng-repeat="item in arr">{{item}}</li>
    </ul
    <!-- 指定唯一值id为索引 -->
    <ul>
        <li ng-repeat="item in arr track by item.id">{{item.name}}</li>
    </ul
  • 使用angular已有的功能。

    比如:

    1. 可以不需要引入jquery, 因为angular已经内置了jQLite
    2. 熟悉内置的过滤器和指令,避免重复造轮子。
  • 使用ng-cloak/ng-bind指令来代替{{}}避免在页面初始化的时候闪烁。

     <div id="template1" ng-cloak>hello</div>
  • 离开页面时注销不需要的监听器、定时器和事件绑定。

    因为这些操作可能会在页面跳转后继续执行,影响性能

    var destroyNameWatch = $scope.$watch('user.name', function(){
        ....
    });
    var timer = $interval(function(){
        ....
    });
    
    // 监听页面离开事件
    $scope.$on('$destroy', function(){
        // 注销监听器
        destroyNameWatch();
        // 清除定时器
        $interval(timer);
    });
  • 总是在指令的compile函数或postLink函数中进行DOM的操作。

    Angular指令编译过程:

    1. 找到ng-app指令节点;
    2. 获取节点范围内的所有DOM, 调用$compile服务对DOM进行编译;
    3. 遍历整个DOM,查找到指令节点并根据指令中的配置参数(template,place,transclude等)进行处理;
    4. 执行各指令的compile函数(如果有);
    5. 执行link函数,进行数据绑定和监听。
  • 保持业务逻辑与界面分离。

    ViewMode(controller)应该总是专注于监听来自view(模板/directive)行为,然后调用相应的Model(service)中的方法。避免在controller中进行DOM的操作,而是应该根据数据驱动的模式,通过改变数据来更新DOM, 或者将复杂的DOM操作封装成指令。

  • 避免在$rootScope中添加函数、业务逻辑。

    公共,可复用的函数应该写成service$rootScope应该用来保存共享的数据。

[译] Async 函数,让promise更友好!

原文链接

Async 函数是一个非常神奇的东西,它将会在Chrome 55中得到默认支持。它允许你书写基于promise的代码,但它看起来就跟同步的代码一样,而且不会阻塞线程。所以,它让你的异步代码看起来并没有那么"聪明"却更具有可读性。

Async 函数的代码示例:

async function myFirstAsyncFunction() {
  try {
    const fulfilledValue = await promise;
  }
  catch (rejectedValue) {
    // …
  }
}

如果你在一个函数声明的的前面使用async关键字,那你就可以在这个函数内使用await。当你去await一个promise的时候,这个函数将会以非阻塞的方式暂停,直到promise处于settled状态。如果这个Promise返回的是成功的状态,你将会得到返回值,如果返回的是失败的状态,那失败的信息将会被抛出。

⭐ 提示: 如果你对promises不熟悉,请查看我们的promises指南

示例1: 打印响应信息

假设我们想要请求一个URL然后把响应信息打印出来,下面是使用promise的示例代码:

function logFetch(url) {
  return fetch(url)
    .then(response => response.text())
    .then(text => {
      console.log(text);
    }).catch(err => {
      console.error('fetch failed', err);
    });
}

下面用async 函数来实现同样的功能:

async function logFetch(url) {
  try {
    const response = await fetch(url);
    console.log(await response.text());
  }
  catch (err) {
    console.log('fetch failed', err);
  }
}

可以看到代码行数和上例一样,但是使用async函数的方式使得所有的回调函数都不见了!这让我们的代码非常容易阅读,特别是那些对promise不是特别熟悉的同学。

⭐ 提示: 你await的任何值都是通过Promise.resolve()来传递的,所以你可以安全放心地使用非原生的promise.

Async 函数的返回值

不管你是否在函数内部使用了await, Async 函数总是返回一个promise 。当 async函数显示滴返回任意值时,返回的promise将会调用resolve方法, 当async函数抛出异常错误时,返回的promise将会调用reject方法,所以:

// wait ms milliseconds
function wait(ms) {
  return new Promise(r => setTimeout(r, ms));
}

async function hello() {
  await wait(500);
  return 'world';
}

当执行hello()时,返回一个执行成功并且传递的值为worldpromise.

async function foo() {
  await wait(500);
  throw Error('bar');
}

当执行hello()时,返回一个执行失败并且传递的值为Error('bar')promise.

示例2: 流式地接收响应数据

在更复杂点的案例中, async函数更能体现其优越性。假设我们想要在输出chunks数据时处理响应信息, 并返回最终的信息长度。

⭐ 提示: "输出chunks" 从我口里说出来让我感觉很别扭.

下面是使用promise的方式:

function getResponseSize(url) {
  return fetch(url).then(response => {
    const reader = response.body.getReader();
    let total = 0;

    return reader.read().then(function processResult(result) {
      if (result.done) return total;

      const value = result.value;
      total += value.length;
      console.log('Received chunk', value);

      return reader.read().then(processResult);
    })
  });
}

你可以看到为了创建一个异步处理的循环,我不得不在processResult函数里调用了它自己本身,这让我感觉自己很聪明,但是,和大多数‘聪明’的代码一样,你必须花很长的时间紧盯着它去弄明白它用来做什么的,就好像你盯着那些90年代的魔幻照片一样。

让我们用async函数来重写上面的功能:

async function getResponseSize(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  let result = await reader.read();
  let total = 0;

  while (!result.done) {
    const value = result.value;
    total += value.length;
    console.log('Received chunk', value);
    // get the next result
    result = await reader.read();
  }

  return total;
}

所有的‘聪明’的代码都不见了。现在新的异步循环使用了可靠的,看起来无趣的while循环来代替,这使我感觉非常的整洁。更多的是,在将来,我们将会使用async iterators,它将会使用for of循环来代替while循环,那这讲会变得更加整洁!

⭐ 提示: 我对streams比较有好感。如果你对streams不太熟悉,可以看看我的指南

Async 函数的其他语法

我们已经看过了async function() {} 的使用方式,但是async关键字还可以用于其他的函数语法中。

箭头函数
// map some URLs to json-promises
const jsonPromises = urls.map(async url => {
  const response = await fetch(url);
  return response.json();
});

⭐ 提示: array.map(func)不会在乎你给的是否是async函数,它只会把它当做一个返回值是promise的普通函数。所以,第二个回调的执行并不会等待第一个回调中的await处理完成。

对象方法
const storage = {
  async getAvatar(name) {
    const cache = await caches.open('avatars');
    return cache.match(`/avatars/${name}.jpg`);
  }
};

storage.getAvatar('jaffathecake').then();
类方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jaffathecake').then();

⭐ 提示: 类的 constructorsgetters/settings不能是 async 函数。

注意!请避太过顺序性免代码

尽管你正在写的代码看起来是同步的,但请确保你没有错失平行处理的机会。

async function series() {
  await wait(500);
  await wait(500);
  return "done!";
}

上面的代码需要 1000ms才能完成,然而:

async function parallel() {
 const wait1 = wait(500);
 const wait2 = wait(500);
 await wait1;
 await wait2;
 return "done!";
}

上面的代码只需要500ms,因为两个wait在同一时间处理了。

示例3: 顺序输出请求信息

假设我们想要获取一系列的URL响应信息,并将它们尽可能快的按正确的顺序打印出来。
深呼吸....下面就是使用promise来实现的代码:

function logInOrder(urls) {
  // fetch all the URLs
  const textPromises = urls.map(url => {
    return fetch(url).then(response => response.text());
  });

  // log them in order
  textPromises.reduce((chain, textPromise) => {
    return chain.then(() => textPromise)
      .then(text => console.log(text));
  }, Promise.resolve());
}

Yeah, 这达到了目的。我正在使用reduce来处理一串的promise,我太聪明了。这是一个如此聪明的代码,但我们最好不要这样做。

但是,当把上面的代码转换成使用 async函数来实现时,它看起来太有顺序了,以至于会使我们很迷惑:

👎 不推荐 - 太顺序了

async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}

看起来整洁多了,但是我的第二个请求只有在第一个请求被完全处理完成之后才会发出去,以此类推。这个比上面那个promise的实例慢多了。幸好这还有一个中立的方案:

👍 推荐 - 很好而且串行

async function logInOrder(urls) {
  // fetch all the URLs in parallel
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // log them in sequence
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}

In this example, the URLs are fetched and read in parallel, but the "smart" reduce bit is replaced with a standard, boring, readable for-loop.

在这个例子中,全部的url一个接一个被请求和处理,但是那个'聪明的'的reduce被标准的,无趣的和更具可读性的for loop 循环取代了。

浏览器兼容性和解决方法

在我写这篇文章时,Chrome 55已经默认支持async 函数。但是在所有主流浏览器中,它还在开发中:

解决方法 1:Generators

所有的主流浏览器的最新版本都支持generators,如果你正在使用它们,你可以稍稍polyfill一下 async函数.

Babel正可以为你做这些事情,这里有个通过Babel REPL写的示例 - 是不是感觉对转换后的代码很熟悉。这个转换机制是 Babel's es2017 preset的一部分。

⭐ 提示: Babel REPL是一个很有趣的东西,试试吧。

我建议你现在就这样做,因为当你的目标浏览器支持了async函数时,你只需要将Babel从你的项目中去除即可。但是如果你真的不想使用转换工具,你可以使用Babel's polyfill

async function slowEcho(val) {
  await wait(1000);
  return val;
}

当你使用了上面说的polyfill,你可以将上面的代码替换为:

const slowEcho = createAsyncFunction(function*(val) {
  yield wait(1000);
  return val;
});

注意到你通过给createAsyncFunction函数传递了一个generator (function*),然后使用yield 代替 await。除此之外它们的效果一样。

解决方法2: regenerator

如果你想要兼容旧的浏览器,Babel同样也能把generators给转换了,这样你就可以在IE8以上的浏览器中使用async函数,但你需要使用Babeles2017 presetthe es2015 preset

你会看到转换后的代码并不好看,所以请小心代码膨胀。

Async all the things!

一旦所有浏览器都支持async函数了,请在所有返回值是promise的函数上使用async!因为它不仅可以使你的代码更tider, 而且它确保了async函数 总是返回一个 promise

回到 2014 年,我对async函数的出现感到非常激动, 现在很高兴看到它们在浏览器中被支持了。Whoop!

HTTP, TCP基本知识

1. 为什么说TCP是可靠连接而UDP是不可靠连接?

  • TCP是面向连接的协议,通过三次握手建立连接,它还提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
  • UDP只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户端和服务器之间建立一个连接,且没有超时重发等机制,固而传输速度很快。

2. Cache-Control 的值 no-cacheno-store有什么区别?

  • no-cache表示必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌 (ETag),no-cache会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载。
  • 相比之下,no-store更加简单,直接禁止浏览器和所有中继缓存存储返回的任何版本的响应。例如:一个包含个人隐私数据或银行数据的响应。每次用户请求该资源时,都会向服务器发送一个请求,每次都会下载完整的响应。

3. HTTP有什么缺点?

  • 通信使用明文传输,内容可能会被窃听(未加密)。
  • 不验证通信方的身份,因此可能遭遇伪装(不认证)。
  • 无法验证报文的完整性,所以有可能被篡改(无完整性保护)。
  • 请求只能从客户端发起。
  • 每次通信都会发送冗长的首部信息。

4. 对称加密和非对称加密的区别?HTTPS使用哪种加密方式?

  • 对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key)。
  • 非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥(public key)和私钥(private key)。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。其加密与解密速度慢。
  • HTTPS使用两者混合的加密方式,先是将对称加密的密钥使用非对称加密的公钥进行加密,然后发送出去,接收方使用私钥进行解密得到对称加密的密钥,然后双方可以使用对称加密来进行以后的通信。

5. HTTPS会带来哪些性能损耗?

  • 增加延时: 一次完整的握手至少需要两端依次来回两次通信,至少增加延时2 RTT,利用会话缓存从而复用连接,延时也至少1 RTT。
  • 消耗CPU 资源: HTTPS 通信主要包括对对称加解密、非对称加解密,消耗CPU资源。

6. 有哪些方式来优化HTTPS带来的性能损耗?

  • CDN 接入:
    HTTPS 增加的延时主要是传输延时 RTT,RTT 的特点是节点越近延时越小,CDN 天然离用户最近,因此选择使用 CDN 作为 HTTPS 接入的入口,将能够极大减少接入延时。CDN 节点通过和业务服务器维持长连接、会话复用和链路质量优化等可控方法,极大减少 HTTPS 带来的延时。

  • 会话缓存:
    虽然前文提到 HTTPS 即使采用会话缓存也要至少1*RTT的延时,但是至少延时已经减少为原来的一半,明显的延时优化;同时,基于会话缓存建立的 HTTPS 连接不需要服务器使用RSA私钥解密获取 Pre-master 信息,可以省去CPU的消耗。如果业务访问连接集中,缓存命中率高,则HTTPS的接入能力讲明显提升。当前 TRP 平台的缓存命中率高峰时期大于30%,10k/s的接入资源实际可以承载13k/的接入,收效非常可观。

  • 硬件加速:
    为接入服务器安装专用的 SSL 硬件加速卡,作用类似 GPU,释放 CPU,能够具有更高的 HTTPS 接入能力且不影响业务程序的。测试某硬件加速卡单卡可以提供 35k 的解密能力,相当于175核 CPU,至少相当于7台24核的服务器,考虑到接入服务器其它程序的开销,一张硬件卡可以实现接近10台服务器的接入能力。

  • 远程解密:
    本地接入消耗过多的 CPU 资源,浪费了网卡和硬盘等资源,考虑将最消耗 CPU 资源的RSA解密计算任务转移到其它服务器,如此则可以充分发挥服务器的接入能力,充分利用带宽与网卡资源。远程解密服务器可以选择 CPU 负载较低的机器充当,实现机器资源复用,也可以是专门优化的高计算性能的服务器。当前也是 CDN 用于大规模HTTPS接入的解决方案之一。

  • SPDY/HTTP2:
    前面的方法分别从减少传输延时和单机负载的方法提高 HTTPS 接入性能,但是方法都基于不改变 HTTP 协议的基础上提出的优化方法,SPDY/HTTP2 利用 TLS/SSL 带来的优势,通过修改协议的方法来提升 HTTPS 的性能,提高下载速度等。

6. TCP的快速重传机制?

  • TCP引入了一种叫Fast Retransmit 的算法,不以时间驱动,而以数据驱动重传。也就是说,如果,包没有连续到达,就ack最后那个可能被丢了的包,如果发送方连续收到3次相同的ack,就重传。Fast Retransmit的好处是不用等timeout了再重传。
  • 比如:如果发送方发出了1,2,3,4,5份数据,第一份先到送了,于是就ack2,结果2因为某些原因没收到,3到达了,于是还是ack2,后面的45都到了,但是还是ack2,因为2还是没有收到,于是发送端收到了三个ack=2的确认,知道了2还没有到,于是就马上重转2。然后,接收端收到了2,此时因为3,4,5都收到了,于是ack6

7. 拥塞控制主要的四个算法?

  • 慢启动
  • 拥塞避免
  • 拥塞发生
  • 快速恢复

React快速理解

背景

Facebook的开发者当时在开发一个广告系统,因为对当前所有的MVC框架不满意,所以就自己写了一个UI框架,于是就有了React。后来因为觉得实在是好用,所以在2013年月份开源的此框架。经过这几年的沉淀,React越来越强大,也受到了越来越多的开发者喜爱。

特性

声明式的: React 可以让你更轻松地创建用户界面。为你的应用的每个状态设计一个简单的Ui, 当你的应用数据改变时,React会非常高效地完成界面的更新。声明式的用户界面让你的代码可以预测,易于理解和容易调试。
基于组件的: 构建一个独立的组件,它们管理这各自的状态,然后将他们组合在一起构成复杂的用户界面。因为组件逻辑不是使用模板而是使用JavaScript来控制,所以你可以很容易你的应用数据并且保持状态与DOM分离。
Learn Once, Write Anywhere: 我们没有限定你的技术栈,所以你可以使用React来开发新功能,而不用重写已有的代码。React也可以使用Node实现服务端渲,还可以使用React Native 构建强大的手机应用。

价值

在年初的React开发者大会上,React的项目经理Tom Occhino讲述了React的最大的价值,React最大的价值不是高性能的虚拟DOM封装的事件机制服务器端渲染,而是声明式的直观的编码方式。React号称能让新人第一天开始使用就能开发新功能。简单的编码方式会让新手能很快地上手,同时也降低了代码维护的成本。这一特性决定了React能快速引起开发者的兴趣并广泛传播的基础。

Virtual DOM

传统应用,直接操作DOM:

图片来源

React应用,操作虚拟DOM:

图片来源

Components

React 是基于组件的,组件可以看做是Virtual DOM的节点。声明式的组件简洁优美:

class Text extends React.Component {
  render() {
    return <p>{this.props.children}</p>;
  }
}
React.render(<Text>Hello World</Text>, document.body);

组件的生命周期

React组件提供了10个生命周期的API,提供的API越多,组件的可空性,灵活性越强。

图片来源

states & update

页面如何更新?或者组件如何更新?
React通过调用setState方法改变states,调用组件的render方法,进行组件的diff,最后最小化更新组件UI。从而实现组件的高效更新。

图片来源

koa-router 源码浅析(2)

上一篇主要将koa-router的整体代码结构和大概的执行流程画了出来,画的不够具体。那这篇主要讲koa-router中的几处的关键代码解读一下。

读代码首先要找到入口文件,那几乎所有的node模块的入口文件都会在package.json文件中的main属性指明了。koa-router的入口文件就是lib/router.js

第三方模块

首先先讲几个第三方的node模块了解一下,因为后面的代码讲解中会用到,不去看具体实现,只要知道其功能就行:
koa-compose:
提供给它一个中间件数组, 返回一个顺序执行所有中间件的执行函数。
methods:
node中支持的http动词,就是http.METHODS,可以在终端输出看看。
path-to-regexp:
将路径字符串转换成强大的正则表达式,还可以输出路径参数。

Router & Layer

RouterLayer 分别是两个构造函数,分别在router.jslayer.js中,koa-router的所有代码也就在这两个文件中,可以知道它的代码量并不是很多。

Router: 创建管理整个路由模块的实例

function Router(opts) {
  if (!(this instanceof Router)) {
    return new Router(opts);
  }

  this.opts = opts || {};
  this.methods = this.opts.methods || [
    'HEAD',
    'OPTIONS',
    'GET',
    'PUT',
    'PATCH',
    'POST',
    'DELETE'
  ];

  this.params = {};
  this.stack = [];
};

首先是

if (!(this instanceof Router)) {
  return new Router(opts);
}

这是常用的去new的方式,所以我们可以在引入koa-router时:

const router = require('koa-router')()

而不用:

const router = new require('koa-router')() // 这样也是没问题的

this.methods:
在后面要讲的allowedMethods方法中要用到的,目的是响应options请求和请求出错的处理。

this.params:
全局的路由参数处理的中间件组成的对象。

this.stack:
其实就是各个路由(Layer)实例组成的数组。每次处理请求时都需要循环这个数组找到匹配的路由。

Layer: 创建各个路由实例

function Layer(path, methods, middleware, opts) {
  ...

  this.stack = Array.isArray(middleware) ? middleware : [middleware];

  // 为给后面的allowedMthods处理
  methods.forEach(function(method) {
    var l = this.methods.push(method.toUpperCase());
    if (this.methods[l-1] === 'GET') {
      // 如果是get请求,则支持head请求
      this.methods.unshift('HEAD');
    }
  }, this);

  // 确保路由的每个中间件都是函数
  this.stack.forEach(function(fn) {
    var type = (typeof fn);
    if (type !== 'function') {
      throw new Error(
        methods.toString() + " `" + (this.opts.name || path) +"`: `middleware` "
        + "must be a function, not `" + type + "`"
      );
    }
  }, this);
  this.path = path;
  // 利用path-to-rege模块生产的路径的正则表达式
  this.regexp = pathToRegExp(path, this.paramNames, this.opts);

  ...
};

这里的this.stackRouter中的不同,这里的是路由所有的中间件的数组。(一个路由可以有多个中间件)

router.register()

作用:注册路由

从上一篇的代码结构图中可以看出,Router的几个实例方法都直接或简介地调用了register方法,可见,它应该是比较核心的函数, 代码不长,我们一行行看一下:

Router.prototype.register = function (path, methods, middleware, opts) {
  opts = opts || {};
  var router = this;

  // 全部路由
  var stack = this.stack;

  // 说明路由的path是支持数组的
  // 如果是数组的话,需要递归调用register来注册路由
  // 因为一个path对应一个路由
  if (Array.isArray(path)) {
    path.forEach(function (p) {
      router.register.call(router, p, methods, middleware, opts);
    });

    return this;
  }

  // 创建路由,路由就是Layer的实例
  // mthods 是路由处理的http方法
  // 最后一个参数对象最终是传给Layer模块中的path-to-regexp模块接口调用的
  var route = new Layer(path, methods, middleware, {
    end: opts.end === false ? opts.end : true,
    name: opts.name,
    sensitive: opts.sensitive || this.opts.sensitive || false,
    strict: opts.strict || this.opts.strict || false,
    prefix: opts.prefix || this.opts.prefix || "",
    ignoreCaptures: opts.ignoreCaptures
  });

  // 处理路径前缀
  if (this.opts.prefix) {
    route.setPrefix(this.opts.prefix);
  }

  // 将全局的路由参数添加到每个路由中
  Object.keys(this.params).forEach(function (param) {
    route.param(param, this.params[param]);
  }, this);

  // 往路由数组中添加新创建的路由
  stack.push(route);

  return route;
};

router.verb()

verb => get|put|post|patch|delete
作用:注册路由

这是koa-router提供的直接注册相应http方法的路由,但最终还是会调用register方法如:

router.get('/user', function(ctx, next){...})

和下面利用register方法等价:

router.register('/user', ['get'], [function(ctx, next){...}])

可以看到直接使用router.verb注册路由会方便很多。来看看代码:
你会发现router.js的代码里并没有Router.prototype.get的代码出现,原因是它还依赖了上面提到的methods模块来实现。

// 这里的methods就是上面的methods模块提供的数组
methods.forEach(function (method) {
  Router.prototype[method] = function (name, path, middleware) {
    var middleware;

    // 这段代码做了两件事:
    // 1.name 参数是可选的,所以要做一些参数置换的处理
    // 2.将所有路由中间件合并成一个数组
    if (typeof path === 'string' || path instanceof RegExp) {
      middleware = Array.prototype.slice.call(arguments, 2);
    } else {
      middleware = Array.prototype.slice.call(arguments, 1);
      path = name;
      name = null;
    }

    // 调用register方法
    this.register(path, [method], middleware, {
      name: name
    });

    return this;
  };
});

router.routes()

作用:启动路由

这是在koa中配置路由的重要一步:

var router = require('koa-router')();
...
app.use(router.routes())

就这样,koa-router就启动了,所以我们也一定会很好奇这个routes函数到底做了什么,但可以肯定router.routes()返回了一个中间件函数。
函数体长了一点,简化一下看下整体轮廓:

Router.prototype.routes = Router.prototype.middleware = function () {
  var router = this;
  var dispatch = function dispatch(ctx, next) {
    ...
  }
  dispatch.router = this;
  return dispatch;
};

这里形成了一个闭包,在routes函数内部返回了一个dispatch函数作为中间件。
接下来看下dispatch函数的实现:

var dispatch = function dispatch(ctx, next) {
    var path = router.opts.routerPath || ctx.routerPath || ctx.path;

    // router.match函数内部遍历所有路由(this.stach),
    // 根据路径和请求方法找到对应的路由
    // 返回的matched对象为: 
    /* 
      var matched = {
        path: [], // 保存了path匹配的路由数组
        pathAndMethod: [], // 保存了path和methods都匹配的路由数组
        route: false // 是否有对应的路由
      };
    */
    var matched = router.match(path, ctx.method);
    var layerChain, layer, i;
    if (ctx.matched) {
      ctx.matched.push.apply(ctx.matched, matched.path);
    } else {
      ctx.matched = matched.path;
    }

    // 如果没有对应的路由,则直接进入下一个中间件
    if (!matched.route) return next();

    // 找到正确的路由的path
    var mostSpecificPath = matched.pathAndMethod[matched.pathAndMethod.length - 1].path;
    ctx._matchedRoute = mostSpecificPath;

    // 使用reduce方法将路由的所有中间件形成一条链
    layerChain = matched.pathAndMethod.reduce(function(memo, layer) {

      // 在每个路由的中间件执行之前,根据参数不同,设置 ctx.captures 和 ctx.params
      // 这就是为什么我们可以直接在中间件函数中直接使用 ctx.params 来读取路由参数信息了
      memo.push(function(ctx, next) {

        // 返回路由的参数的key 
        ctx.captures = layer.captures(path, ctx.captures);

        // 返回参数的key和对应的value组成的对象
        ctx.params = layer.params(path, ctx.captures, ctx.params);

        // 执行下一个中间件
        return next();
      });

      // 将上面另外加的中间件和已有的路由中间件合并到一起
      // 所以最终 layerChain 将会是一个中间件的数组
      return memo.concat(layer.stack);
    }, []);

    // 最后调用上面提到的 compose 模块提供的方法,返回将 layerChain (中间件的数组) 
    // 顺序执行所有中间件的执行函数, 并立即执行。
    return compose(layerChain)(ctx, next);
  };

router.allowMethods()

作用: 当请求出错时的处理逻辑

同样也是koa中配置路由的中一步:

var router = require('koa-router')();
...
app.use(router.routes())
app.use(router.allowMethods())

可以看出,该方法也是闭包内返回了中间件函数。我们将代码简化一下:

Router.prototype.allowedMethods = function (options) {
  options = options || {};
  var implemented = this.methods;
  return function allowedMethods(ctx, next) {
    return next().then(function() {
      var allowed = {};

      if (!ctx.status || ctx.status === 404) {
        ...

        if (!~implemented.indexOf(ctx.method)) {
          if (options.throw) {
            ...
          } else {
            ctx.status = 501;
            ctx.set('Allow', allowedArr);
          }
        } else if (allowedArr.length) {
          if (ctx.method === 'OPTIONS') {
            ctx.status = 204;
            ctx.set('Allow', allowedArr);
          } else if (!allowed[ctx.method]) {
            if (options.throw) {
              ...
            } else {
              ctx.status = 405;
              ctx.set('Allow', allowedArr);
            }
          }
        }
      }
    });
  };
};

眼尖的同学可能会看到一些http code404, 501, 204, 405
那这个函数其实就是当所有中间件函数执行完了,并且请求出错了进行相应的处理:

  1. 如果请求的方法koa-router不支持并且没有设置throw选项,则返回 501(未实现)
  2. 如果是options请求,则返回 204(无内容)
  3. 如果请求的方法支持但没有设置throw选项,则返回 405(不允许此方法 )

总结

粗略浅析了这么些,能大概知道了koa-router的工作原理。笔者能力有限,有错误还请指出。

ES6随笔

随时、随地、随心情记录 💢

1

bigbang
sfsdfdf

dfsdff

const a = '2312'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.