Express.js的上述代码其实也是实现了类似的效果,setprototypeof又是一个第三方库,作用类似Object.setPrototypeOf(obj, prototype),就是给一个对象设置原型,setprototypeof存在的意义就是兼容老标准的JS,也就是加了一些polyfill,他的代码在这里。所以:
setPrototypeOf(router, proto);这行代码的意思就是让router.__proto__指向proto,router是你在new proto()时的返回对象,执行了上面这行代码,这个router就可以拿到proto上的全部方法了。像router.handle这种方法就可以挂载到proto上了,成为proto.handle。
绕了一大圈,其实就是JS面向对象的使用,给router添加类方法,但是为什么使用这么绕的方式,而不是像我上面那个Class那样用呢?这我就不是很清楚了,可能有什么历史原因吧。
路由架构Router的基本结构知道了,要理解Router的具体代码,我们还需要对Express的路由架构有一个整体的认识。就以我们这两个示例API来说:
get /api/users
post /api/users
我们发现他们的path是一样的,都是/api/users,但是他们的请求方法,也就是method不一样。Express里面将path这一层提取出来作为了一个类,叫做Layer。但是对于一个Layer,我们只知道他的path,不知道method的话,是不能确定一个路由的,所以Layer上还添加了一个属性route,这个route上也存了一个数组,数组的每个项存了对应的method和回调函数handle。整个结构你可以理解成这个样子:
const router = { stack: [ // 里面很多layer { path: '/api/users' route: { stack: [ // 里面存了多个method和回调函数 { method: 'get', handle: function1 }, { method: 'post', handle: function2 } ] } } ] }知道了这个结构我们可以猜到,整个流程可以分成两部分:注册路由和匹配路由。当我们写app.get和app.post这些方法时,其实就是在router上添加layer和route。当一个网络请求过来时,其实就是遍历layer和route,找到对应的handle拿出来执行。
注意route数组里面的结构,每个项按理来说应该使用一种新的数据结构来存储,比如routeItem之类的。但是Express并没有这样做,而是将它和layer合在一起了,给layer添加了method和handle属性。这在初次看源码的时候可能造成困惑,因为layer同时存在于router的stack上和route的stack上,肩负了两种职责。
router.route这个方法是我们前面注册路由的时候调用的一个方法,回顾下前面的注册路由的方法,比如app.get:
app.get = function (path) { this.lazyrouter(); var route = this._router.route(path); route.get.apply(route, Array.prototype.slice.call(arguments, 1)); return this; }结合上面讲的路由架构,我们在注册路由的时候,应该给router添加对应的layer和route,router.route的代码就不难写出了:
proto.route = function route(path) { var route = new Route(); var layer = new Layer(path, route.dispatch.bind(route)); // 参数是path和回调函数 layer.route = route; this.stack.push(layer); return route; } Layer和Route构造函数上面代码新建了Route和Layer实例,这两个类的构造函数其实也挺简单的。只是参数的申明和初始化:
// layer.js module.exports = Layer; function Layer(path, fn) { this.path = path; this.handle = fn; this.method = ''; } // route.js module.exports = Route; function Route() { this.stack = []; this.methods = {}; // 一个加快查找的hash表 } route.get前面我们看到了app.get其实通过下面这行代码,最终调用的是route.get:
route.get.apply(route, Array.prototype.slice.call(arguments, 1));也知道了route.get这种动词处理函数,其实就是往route.stack上添加layer,那我们的route.get也可以写出来了:
var methods = ["get", "post"]; methods.forEach(function (method) { Route.prototype[method] = function () { // 支持传入多个回调函数 var handles = flatten(slice.call(arguments)); // 为每个回调新建一个layer,并加到stack上 for (var i = 0; i < handles.length; i++) { var handle = handles[i]; // 每个handle都应该是个函数 if (typeof handle !== "function") { var type = toString.call(handle); var msg = "Route." + method + "() requires a callback function but got a " + type; throw new Error(msg); } // 注意这里的层级是layer.route.layer // 前面第一个layer已经做个path的比较了,所以这里是第二个layer,path可以直接设置为/ var layer = new Layer("http://www.likecs.com/", handle); layer.method = method; this.methods[method] = true; // 将methods对应的method设置为true,用于后面的快速查找 this.stack.push(layer); } }; });这样,其实整个router的结构就构建出来了,后面就看看怎么用这个结构来处理请求了,也就是router.handle方法。
router.handle