Express源碼的一些理解

Express框架很早就有接觸,一開始接觸的時候以爲Express框架晦澀難懂繞來繞去有點頭暈。最近血來潮準備再次翻看下Express源碼,一方面也是爲了本身作個筆記,方便之後翻閱查看,另外一方面是爲了和你們分享,但願獲得各位大牛的指導指正。javascript

示例代碼

在開始前對於如何啓動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請求的路由,代碼以下java

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`內部就進行了路由的初始化設置,而且自動加入了兩個新的中間件。
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】, 爲如下形式正則表達式

/** 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內部的巧妙實現express

methods.concat('all').forEach(function(method){
  proto[method] = function(path){
    var route = this.route(path)
    // 這裏將method內部的屬性方法的this改成route對象,並傳值route.get等方法的傳參中除了第一個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中。json

來看下內部的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);,裝載內部的子路由的時候,就像多米勒牌的第一張被觸發了之後出現排山倒海般紙牌組成的美景同樣,內部的實現文件一被碰到就自動實現好了。數據結構

function Route(path) {
  this.path = path;
  this.stack = [];

  debug('new %o', path)

  // route handlers for various http methods
  this.methods = {};
}
複製代碼
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函數來進行調用回調函數。app

初始化好了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;
};
複製代碼

最終組裝好的第一層的形式以下 函數

最終效果的打印結果

從這裏咱們看到內部嵌套的仍是比較多的,內部的stack至少有三層,一開始看到時候仍是比較頭暈。

關於路由匹配以後會補上》》》》》to be continued...

總結

總之,要理解這麼多層嵌套的關鍵是須要搞清楚,三層中this的指代,第一層是初始化路由, this指代router對象,第二層是內部自動組裝的get等一些子路由請求,this指代的是express.Router(); 第三層是也是內部自動組裝,不過這一層會放上真正的請求地址,stack放置在layer.route下,this指代的是Route對象(new Route(path);),每一層都有一個stack數組進行Layer的存放,以後就會經過解析正則表達式的路由規則和監聽的請求地址進行匹配,最終會調用Layer內部的handle函數,從而進行頁面交互和渲染。

學習源碼的過程是痛苦的,但理解完之後會對整個機制有更加深入的理解,最後若是有什麼不足之處還須要各位大牛的指正。

相關文章
相關標籤/搜索