探討Express Router & Route

Express

基於 Node.js 平臺,快速、開放、極簡的 web 開發框架

安裝

//應用生成器工具
npm install express-generator -g

//建立express應用包
express app

//安裝依賴
npm install

成功生成後,會產生如下的目錄和文件:node

|---bin
|---node_module
|---public
|---routes
|---view
|---app.js
|---package.json

接下來咱們經過:web

npm start

啓動程序後,訪問127.0.0.1:3000,就能訪問到express的頁面了。express

接下來經過研究源碼,來探討express路由原理的實現。npm

路由

咱們經過查看app.jsindex.js文件:json

app.jsapp

var index = require('./routes/index');

app.use('/', index);

//或
app.get('/', index);

routes/index.js框架

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

router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

能夠看出,express的路由大概實現 定義一份路由規則文件,再經過app.use()或者app[METHOD]來創建路由規則訪問聯繫,雖然二者的結果同樣,可是存在本質上的區別。工具

下圖是主要涉及的幾個文件:ui

圖片描述

接下來咱們經過源碼首先看看app.use()具體是一個什麼樣實現思路。this

app.use

咱們打開node_module裏的express文件夾。打開lib/application.js文件。

app.use = function use(fn) {
    var offset = 0;
    var path = '/';

    // default path to '/'
    // disambiguate app.use([fn])
    if (typeof fn !== 'function') {
        var arg = fn;

        while (Array.isArray(arg) && arg.length !== 0) {
            arg = arg[0];
        }

        // first arg is the path
        if (typeof arg !== 'function') {
            offset = 1;
            path = fn;
        }
    }

    var fns = flatten(slice.call(arguments, offset));

    if (fns.length === 0) {
        throw new TypeError('app.use() requires middleware functions');
    }

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

        debug('.use app under %s', path);
        fn.mountpath = path;
        fn.parent = this;

        // restore .app property on req and res
        router.use(path, function mounted_app(req, res, next) {
            var orig = req.app;
            fn.handle(req, res, function(err) {
                setPrototypeOf(req, orig.request)
                setPrototypeOf(res, orig.response)
                next(err);
            });
        });

        // mounted an app
        fn.emit('mount', this);
    }, this);

    return this;
};

看到use裏部分的代碼,開始作了判斷處理use掛載的是路徑仍是function,而且經過lazyrouter()方法實例router類,而且全局只存在一個router實例對象,最終調用router.use()方法。

接着,咱們到lib/router/index.js 看router.use方法的實現:

proto.use = function use(fn) {
  var offset = 0;
  var path = '/';

  // default path to '/'
  // disambiguate router.use([fn])
  if (typeof fn !== 'function') {
    var arg = fn;

    while (Array.isArray(arg) && arg.length !== 0) {
      arg = arg[0];
    }

    // first arg is the path
    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 middleware functions');
  }

  for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[i];

    if (typeof fn !== 'function') {
      throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
    }

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

經過對比app.use方法,router.use前半部分處理相同,但後面實例化一個Layer類,而且丟進stack裏。

Layer類保存Router和Route一些數據信息:

圖片描述

相同點:

path都是存放掛載路徑,options.end用來判斷是不是路由中間件。

不一樣點:

Router和Route的區別是一個是添非路由中間件,另外一個是添加路由中間件。

他們的layer.route指向也不同,一個指向undefined,另外一個沒有route屬性。

文章進行到一半,咱們小總結一下,app.use()方法是用來添加非路由中間件的,最終是調用router實例方法,會實例劃一個Layer類對象用於存放數據,而且把layer對象push進router.stack裏,全局只有一個router。

app[METHOD]

咱們經過源碼去探討路由中間件app[METHOD]是一個怎樣的原理:

app.jsapp.use('\',index)改爲app.get('\',index).

application.js:

methods.forEach(function(method) {
    app[method] = function(path) {
        if (method === 'get' && arguments.length === 1) {
            // app.get(setting)
            return this.set(path);
        }

        this.lazyrouter();

        var route = this._router.route(path);
        route[method].apply(route, slice.call(arguments, 1));
        return this;
    };
});

能夠看出,代碼裏作了一個app.get方法的判斷處理,get方法只有一個參數時,是獲取app的本地變量,後面仍是實例化router對象,而且用router上的route方法放回的對象去調用Route類上的route[METHOD].

/lib/router/route.js

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

在route裏有一個實例化的layer,且放在stack裏,與Router的layer不一樣的是,Route的沒有layer.route且layer.method存放http方法。

到這裏,咱們大概能夠總結下路由中間件和非路由中間件的聯繫,以下圖:

圖片描述

app初始化時,會push兩個方法(init,query)進router.stack裏。咱們能夠經過app.use往app添加非路由中間件,也能夠經過app[METHOD]添加路由中間件,一樣是push layer實例對象,但route是指向Route實例化的對象。

完整的Router邏輯過程,如圖:

圖片描述

總結

  1. express中添加中間件方法有app.use和app[METHOD],固然還有內置的Router類,app.use用來添加非路由中間件,app[METHOD]用來添加路由中間件。
  2. Layer類封裝中間的path和handle(fns的處理)
  3. Router和Route都有對應的stack,可是Route在整個app中只有一個,而Route能夠又多個。放在Router stack裏的路由中間件,經過Layer.route指向Route,與Route stack相關聯起來
相關文章
相關標籤/搜索