.6-浅析express源码之Router模块(2)-router.use
这一节继续深入Router模块,首先从最常用的use开始。
router.use
方法源码如下:
proto.use = function use(fn) { var offset = 0; var path = '/'; if (typeof fn !== 'function') { var arg = fn; while (Array.isArray(arg) && arg.length !== 0) arg = arg[0]; if (typeof arg !== 'function') { offset = 1; path = fn; } } var callbacks = flatten(slice.call(arguments, offset)); if (callbacks.length === 0) throw new TypeError('Router.use() requires a middleware function') for (var i = 0; i < callbacks.length; i++) { var fn = callbacks[i]; if (typeof fn !== 'function') throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn)) debug('use %o %s', path, fn.name || '<anonymous>'); // 内部模块layer! var layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, end: false }, fn); // 通过use方法生成的layer没有route值 layer.route = undefined; // 初始化时定义的数组 this.stack.push(layer); } return this; };
前半部分十分熟悉,根本就是app.use的翻版。
当然,最后遍历中间件函数处理的时候就不一样了,引入了新的本地模块Layer。
Layer
不太理解这个层的意义,无论是app.use还是router.use,每一个中间件都会生成一个layer对象,然后push进router上的stack数组。
那么多路径呢,是否会生成多个layer?答案是否。
看一眼layer的构造函数:
function Layer(path, options, fn) { if (!(this instanceof Layer)) { return new Layer(path, options, fn); } debug('new %o', path) var opts = options || {}; /** * layer.handle => 中间件函数 * layer.name => 函数名 * layer.regexp => 路径的正则 */ this.handle = fn; this.name = fn.name || '<anonymous>'; this.params = undefined; this.path = undefined; this.regexp = pathRegexp(path, this.keys = [], opts); // 快速匹配标记 this.regexp.fast_star = path === '*' this.regexp.fast_slash = path === '/' && opts.end === false }
其中比较关键一步是根据传进来的path生成一个正则,pathRegexp是一个工具模块,无论传进去的是字符串、数组、正则都能返回一个正则匹配需要的值。
简略的看一下工具核心源码:
function pathtoRegexp(path, keys, options) { // ... // 字符串 path = ('^' + path + (strict ? '' : path[path.length - 1] === '/' ? '?' : '/?')); // ...后面有很多replace // 数组 if (Array.isArray(path)) { path = path.map(function(value) { return pathtoRegexp(value, keys, options).source; }); // 使用|分割多个规则来进行多重匹配 return new RegExp('(?:' + path.join('|') + ')', flags); } // 正则 比较简单的 // var MATCHING_GROUP_REGEXP = /\((?!\?)/g; if (path instanceof RegExp) { // 匹配组 while (m = MATCHING_GROUP_REGEXP.exec(path.source)) { keys.push({ name: name++, optional: false, offset: m.index }); } return path; } }
字符串模式非常复杂,因为允许类正则写法的字符串,解析会变得十分复杂,后面有很多很多的replace,这里给一个开头,比较简单过把瘾。
最后返回一个匹配路径的正则表达式,然后在该对象上加两个标记,比如说如果一个Layer的正则对象有全局路由标记,则根本不用正则校验,直接可以调用中间件。
返回Layer对象后,该对象会被push进router的stack数组。
这节就简单过一下Router模块的use方法,下一节看看具体请求方法的源码流向。
老子要日穿V8引擎