• .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方法,下一节看看具体请求方法的源码流向。

  • 相关阅读:
    Android 音视频同步机制
    FFmpeg命令行工具学习(五):FFmpeg 调整音视频播放速度
    Android框架式编程之RxJava
    Android Gradle 学习笔记(一):Gradle 入门
    FFmpeg开发实战(六):使用 FFmpeg 将YUV数据编码为视频文件
    SDL 开发实战(七): SDL 多线程与锁机制
    JNI实战(四):C 调用 Java
    JNI实战(三):JNI 数据类型映射
    JNI实战(二):Java 调用 C
    JNI实战(一):JNI HelloWorld
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8868633.html
Copyright © 2020-2023  润新知