手写@koa/router源码 (2)

匹配路由就是当一个请求来了我们就去遍历router上的所有layer,找出path匹配的layer,再找出layer上method匹配的route,然后将对应的回调函数handle拿出来执行。

@koa/router有着类似的架构,他的代码就是在实现这种架构,先带着这种架构思维,我们可以很容易读懂他的代码。

Router类

首先肯定是Router类,他的构造函数也比较简单,只需要初始化几个属性就行。由于@koa/router模块大量使用了面向对象的思想,如果你对JS的面向对象还不熟悉,可以先看看这篇文章。

module.exports = Router; function Router() { // 支持无new直接调用 if (!(this instanceof Router)) return new Router(); this.stack = []; // 变量名字都跟Express.js的路由模块一样 }

上面代码有一行比较有意思

if (!(this instanceof Router)) return new Router();

这种使用方法我在其他文章也提到过:支持无new调用。我们知道要实例化一个类,一般要使用new关键字,比如new Router()。但是如果Router构造函数加了这行代码,就可以支持无new调用了,直接Router()可以达到同样的效果。这是因为如果你直接Router()调用,this instanceof Router返回为false,会走到这个if里面去,构造函数会帮你调用一下new Router()。

所以这个构造函数的主要作用就是初始化了一个属性stack,嗯,这个属性名字都跟Express.js路由模块一样。前面的架构已经说了,这个属性就是用来存放layer的。

Router构造函数官方源码:

请求动词函数

前面架构讲了,作为一个路由模块,我们主要解决两个问题:注册路由匹配路由

先来看看注册路由,注册路由主要是在请求动词函数里面进行的,比如router.get和router.post这种函数。HTTP动词有很多,有一个库专门维护了这些动词:methods。@koa/router也是用的这个库,我们这里就简化下,直接一个将get和post放到一个数组里面吧。

// HTTP动词函数 const methods = ["get", "post"]; for (let i = 0; i < methods.length; i++) { const method = methods[i]; Router.prototype[method] = function (path, middleware) { // 将middleware转化为一个数组,支持传入多个回调函数 middleware = Array.prototype.slice.call(arguments, 1); this.register(path, [method], middleware); return this; }; }

上面代码直接循环methods数组,将里面的每个值都添加到Router.prototype上成为一个实例方法。这个方法接收path和middleware两个参数,这里的middleware其实就是我们路由的回调函数,因为代码是取的arguments第二个开始到最后所有的参数,所以其实他是支持同时传多个回调函数的。另外官方源码其实是三个参数,还有可选参数name,因为是可选的,跟核心逻辑无关,我这里直接去掉了。

还需要注意这个实例方法最后返回了this,这种操作我们在Koa源码里面也见过,目的是让用户可以连续点点点,比如这样:

router.get().post();

这些实例方法最后其实都是调this.register()去注册路由的,下面我们看看他是怎么写的。

请求动词函数官方源码:

router.register()

router.register()实例方法是真正注册路由的方法,结合前面架构讲的,注册路由就是构建layer的数据结构可知,router.register()的主要作用就是构建这个数据结构:

Router.prototype.register = function (path, methods, middleware) { const stack = this.stack; const route = new Layer(path, methods, middleware); stack.push(route); return route; };

代码跟预期的一样,就是用path,method和middleware来创建一个layer实例,然后把它塞到stack数组里面去。

router.register官方源码:

Layer类

上面代码出现了Layer这个类,我们来看看他的构造函数吧:

const { pathToRegexp } = require("path-to-regexp"); module.exports = Layer; function Layer(path, methods, middleware) { // 初始化methods和stack属性 this.methods = []; // 注意这里的stack存放的是我们传入的回调函数 this.stack = Array.isArray(middleware) ? middleware : [middleware]; // 将参数methods一个一个塞进this.methods里面去 for (let i = 0; i < methods.length; i++) { this.methods.push(methods[i].toUpperCase()); // ctx.method是大写,注意这里转换为大写 } // 保存path属性 this.path = path; // 使用path-to-regexp库将path转化为正则 this.regexp = pathToRegexp(path); }

从Layer的构造函数可以看出,他的架构跟Express.js路由模块已经有点区别了。Express.js的Layer上还有Route这个概念。而@koa/router的stack上存的直接是回调函数了,已经没有route这一层了。我个人觉得这种层级结构是比Express的要清晰的,因为Express的route.stack里面存的又是layer,这种相互引用是有点绕的,这点我在Express源码解析中也提出过。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsjzz.html