对于Express源码的一些理解

栏目: Node.js · 发布时间: 5年前

内容简介:Express框架很早就有接触,一开始接触的时候觉得Express框架晦涩难懂绕来绕去有点头晕。最近血来潮准备再次翻看下Express源码,一方面也是为了自己做个笔记,方便以后翻阅查看,另一方面是为了和大家分享,希望得到各位大牛的指导指正。在开始前对于如何启动express项目在这里就不再赘诉了,不过一般情况都有以下几句常见代码这里重点讲下导入的users.js依赖包,因为这个关系到get请求的路由,代码如下

Express框架很早就有接触,一开始接触的时候觉得Express框架晦涩难懂绕来绕去有点头晕。最近血来潮准备再次翻看下Express源码,一方面也是为了自己做个笔记,方便以后翻阅查看,另一方面是为了和大家分享,希望得到各位大牛的指导指正。

示例代码

在开始前对于如何启动express项目在这里就不再赘诉了,不过一般情况都有以下几句常见代码

var express = require('express');
// index下的路由规则
var index = require('./routes/index');
// user下的路由规则
var users = require('./routes/users');
var app = express();

app.use('/', index);
app.use('/users', users);

// 监听3000端口
app.listen(3000, () => {
  console.log("listening.... ")
});
复制代码

这里重点讲下导入的users.js依赖包,因为这个关系到get请求的路由,代码如下

var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function (req, res, next) {
    res.json({
        name: 1
    });
    next();
},
function (req, res, next) {
    console.log("next aaaa")
});

router.get('/aaa', function (req, res, next) {
    next();
}, function (req, res, next) {
    res.json({
        a: 1
    });
    next();
}, function (req, res) {
    console.log("next", 2222);
});


module.exports = router;
复制代码

源码分析

从示例代码里我们不难看最重要的代码是`app.use`,use内部存放的都是`middleware`,当路由地址匹配的时候,会依次流过这些`middleware`。不过在进行中间件的use的时候,首先是对路由的初始化设置,`lazyrouter`内部就进行了路由的初始化设置,并且自动加入了两个新的中间件。 ```javascript app.lazyrouter = function lazyrouter() { if (!this._router) { this._router = new Router({ caseSensitive: this.enabled('case sensitive routing'), strict: this.enabled('strict routing') });

this._router.use(query(this.get('query parser fn')));
this._router.use(middleware.init(this));
复制代码

} };

铺垫了这么多,最终的构造出来的`router对象【1】`, 为以下形式

```javascript
/**
router方法对象内部属性有:
  router.params = {};
  router._params = [];
  router.caseSensitive = opts.caseSensitive;
  router.mergeParams = opts.mergeParams;
  router.strict = opts.strict;
  router.stack = [];
**/
function router(req, res, next) {
    router.handle(req, res, next);
}
复制代码

在这里值得一提的是express内部的巧妙实现

methods.concat('all').forEach(function(method){
  proto[method] = function(path){
    var route = this.route(path)
    route[method].apply(route, slice.call(arguments, 1));
    return this;
  };
});
复制代码
proto.route = function route(path) {
  var route = new Route(path);

  var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
  }, route.dispatch.bind(route));

  layer.route = route;

  this.stack.push(layer);
  return route;
};
复制代码

通过遍历所有的method,往proto内部新增get方法等method内部的属性,首先需要搞清楚this指谁,this指代的是当前的router对象即为express.Router(),内部包含【1】中的router对象一样的数据结构,由于是get请求,所有layer.route即为构造的子路由对象,并且将每个get等请求路由加入到Layer对象中,每一个get,post等请求对应于一个Layer对象,最终push到新的stack中。

来看下内部的Layer对象,我认为最重要的是设置路由正则规则以及存放handle函数

function Layer(path, options, fn) {
  if (!(this instanceof Layer)) {
    return new Layer(path, options, fn);
  }

  debug('new %o', path)
  var opts = options || {};

  this.handle = fn;
  this.name = fn.name || '<anonymous>';
  this.params = undefined;
  this.path = undefined;
  this.regexp = pathRegexp(path, this.keys = [], opts);

  // set fast path flags
  this.regexp.fast_star = path === '*'
  this.regexp.fast_slash = path === '/' && opts.end === false
}
复制代码

之后第二个神奇巧妙的地方就是 var route = new Route(path); ,装载内部的子路由的时候,就像多米勒牌的第一张被触发了出现排山倒海的美景一样,内部的实现文件一被碰到就自动实现好了。

methods.forEach(function(method){
  Route.prototype[method] = function(){
    var handles = flatten(slice.call(arguments));

    for (var i = 0; i < handles.length; i++) {
      var handle = handles[i];

      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);
      }

      debug('%s %o', method, this.path)

      var layer = Layer('/', {}, handle);
      layer.method = method;

      this.methods[method] = true;
      this.stack.push(layer);
    }

    return this;
  };
});
复制代码

通过遍历所有的method,往Route内部新增get方法等method内部的属性,首先需要搞清楚this指谁,this指代的是new Route(path)构造出来的当前子路由对象, handle方法即为get方法的第一个参数之后的回调函数,可以有多个,多个回调函数是惰性的,内部将next方法作为参数传入了回调函数,需要使用next函数来进行调用回调函数。

初始化好了router,接下来就是需要将不同route装载进入express设定的数据结构中,express采用了数组的形式来进行存放,首先让我们来看下app.use的内部部分源码, app.use 可以有两个参数,第一个为非必须参数path,第二个为fn,在最开始的时候会进行路由的初始化操作,最终 app.use 会调用router文件内部的 router.use ,这里会将每个中间件的fn存入到Layer中,一个中间件对应于一个Layer,由于是中间件,所有Layer对象下面的route设置为undefined,区别get等其他请求,最终会将Layer对象push到stack。

app.use = function use(fn) {
  // ...
  // setup router
  this.lazyrouter();
  var router = this._router;

  fns.forEach(function (fn) {
    // non-express app
    if (!fn || !fn.handle || !fn.set) {
      return router.use(path, fn);
    }
  }, this);

  return this;
};
复制代码
proto.use = function use(fn) {
  for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[i];

    // add the middleware
    debug('use %o %s', path, fn.name || '<anonymous>')

    var layer = new Layer(path, {
      sensitive: this.caseSensitive,
      strict: false,
      end: false
    }, fn);

    layer.route = undefined;

    this.stack.push(layer);
  }

  return this;
};
复制代码

最终组装好的第一层的形式如下

对于Express源码的一些理解

最终效果的打印结果

对于Express源码的一些理解

从这里我们看到内部嵌套的还是比较多的,内部的stack至少有三层,一开始看到时候还是比较头晕。

关于路由匹配之后会补上》》》》》to be continued...

总结

总之,要理解这么多层嵌套的关键是需要搞清楚,三层中this的指代,第一层是初始化路由, this指代router对象,第二层是内部自动组装的get等一些子路由请求,this指代的是express.Router(); 第三层是也是内部自动组装,不过这一层会放上真正的请求地址,this指代的是Route对象,每一层都有一个stack数组进行Layer的存放,之后就会通过解析正则表达式的路由规则和监听的请求地址进行匹配,最终会调用Layer内部的handle函数,从而进行页面交互和渲染。

学习源码的过程是痛苦的,但理解完以后会对整个机制有更加深刻的理解,最后如果有什么不足之处还需要各位大牛的指正。


以上所述就是小编给大家介绍的《对于Express源码的一些理解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

失控

失控

[美] 凯文·凯利 / 东西文库 / 新星出版社 / 2010-12 / 88.00元

《失控》,全名为《失控:机器、社会与经济的新生物学》(Out of Control: The New Biology of Machines, Social Systems, and the Economic World)。 2006年,《长尾》作者克里斯·安德森在亚马逊网站上这样评价该书: “这可能是90年代最重要的一本书”,并且是“少有的一年比一年卖得好的书”。“尽管书中的一些例子......一起来看看 《失控》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具