kraaas / blog Goto Github PK
View Code? Open in Web Editor NEW记录日常的思考与总结 :)
记录日常的思考与总结 :)
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">
在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
已有的功能。比如:
jquery
, 因为angular
已经内置了jQLite
。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指令编译过程:
ng-app
指令节点;ViewMode
(controller)应该总是专注于监听来自view
(模板/directive)行为,然后调用相应的Model
(service)中的方法。避免在controller
中进行DOM
的操作,而是应该根据数据驱动
的模式,通过改变数据来更新DOM
, 或者将复杂的DOM操作封装成指令。
$rootScope
中添加函数、业务逻辑。公共,可复用的函数应该写成service
。$rootScope
应该用来保存共享的数据。
Async 函数是一个非常神奇的东西,它将会在Chrome 55
中得到默认支持。它允许你书写基于promise
的代码,但它看起来就跟同步的代码一样,而且不会阻塞线程。所以,它让你的异步代码看起来并没有那么"聪明"却更具有可读性。
Async 函数的代码示例:
async function myFirstAsyncFunction() {
try {
const fulfilledValue = await promise;
}
catch (rejectedValue) {
// …
}
}
如果你在一个函数声明的的前面使用async
关键字,那你就可以在这个函数内使用await
。当你去await
一个promise
的时候,这个函数将会以非阻塞的方式暂停,直到promise
处于settled
状态。如果这个Promise
返回的是成功的状态,你将会得到返回值,如果返回的是失败的状态,那失败的信息将会被抛出。
⭐ 提示: 如果你对
promises
不熟悉,请查看我们的promises指南
假设我们想要请求一个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
.
不管你是否在函数内部使用了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()
时,返回一个执行成功并且传递的值为world
的promise
.
async function foo() {
await wait(500);
throw Error('bar');
}
当执行hello()
时,返回一个执行失败并且传递的值为Error('bar')
的promise
.
在更复杂点的案例中, 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 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(…);
⭐ 提示: 类的
constructors
和getters/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
在同一时间处理了。
假设我们想要获取一系列的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
函数。但是在所有主流浏览器中,它还在开发中:
所有的主流浏览器的最新版本都支持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
。除此之外它们的效果一样。
如果你想要兼容旧的浏览器,Babel
同样也能把generators
给转换了,这样你就可以在IE8以上的浏览器中使用async
函数,但你需要使用Babel
的 es2017 preset和 the es2015 preset
你会看到转换后的代码并不好看,所以请小心代码膨胀。
一旦所有浏览器都支持async
函数了,请在所有返回值是promise
的函数上使用async
!因为它不仅可以使你的代码更tider
, 而且它确保了async
函数 总是返回一个 promise
。
回到 2014 年,我对async
函数的出现感到非常激动, 现在很高兴看到它们在浏览器中被支持了。Whoop!
TCP
是可靠连接而UDP
是不可靠连接?TCP
是面向连接的协议,通过三次握手建立连接,它还提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。UDP
只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户端和服务器之间建立一个连接,且没有超时重发等机制,固而传输速度很快。Cache-Control
的值 no-cache
和 no-store
有什么区别?no-cache
表示必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌 (ETag),no-cache
会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载。no-store
更加简单,直接禁止浏览器和所有中继缓存存储返回的任何版本的响应。例如:一个包含个人隐私数据或银行数据的响应。每次用户请求该资源时,都会向服务器发送一个请求,每次都会下载完整的响应。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 的性能,提高下载速度等。
TCP
引入了一种叫Fast Retransmit
的算法,不以时间驱动,而以数据驱动重传。也就是说,如果,包没有连续到达,就ack最后那个可能被丢了的包,如果发送方连续收到3次相同的ack,就重传。Fast Retransmit
的好处是不用等timeout
了再重传。1,2,3,4,5
份数据,第一份先到送了,于是就ack
回2
,结果2
因为某些原因没收到,3
到达了,于是还是ack
回2
,后面的4
和5
都到了,但是还是ack
回2
,因为2
还是没有收到,于是发送端收到了三个ack=2
的确认,知道了2
还没有到,于是就马上重转2
。然后,接收端收到了2
,此时因为3,4,5
都收到了,于是ack
回6
。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能快速引起开发者的兴趣并广泛传播的基础。
传统应用,直接操作DOM:
图片来源
React应用,操作虚拟DOM:
图片来源
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越多,组件的可空性,灵活性越强。
图片来源
页面如何更新?或者组件如何更新?
React通过调用setState
方法改变states
,调用组件的render方法,进行组件的diff,最后最小化更新组件UI。从而实现组件的高效更新。
图片来源
上一篇主要将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
分别是两个构造函数,分别在router.js
和 layer.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.stack
和Router
中的不同,这里的是路由所有的中间件的数组。(一个路由可以有多个中间件)
作用:注册路由
从上一篇的代码结构图中可以看出,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;
};
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;
};
});
作用:启动路由
这是在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);
};
作用: 当请求出错时的处理逻辑
同样也是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 code
: 404
, 501
, 204
, 405
那这个函数其实就是当所有中间件函数执行完了,并且请求出错了进行相应的处理:
throw
选项,则返回 501(未实现)
options
请求,则返回 204(无内容)
throw
选项,则返回 405(不允许此方法 )
粗略浅析了这么些,能大概知道了koa-router的工作原理。笔者能力有限,有错误还请指出。
随时、随地、随心情记录 💢
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.