express源碼學習

I don't read books, never went to school, I just read other people's code and always wonder how things work. ——TJ Holowaychukjavascript

簡介

這篇文章的主要的目的是經過研究express核心源碼,讓咱們對express深刻理解,不只會用express,還要理解其背後的思想,提升開發效率。研究express源碼,學習大神的代碼結構。本文只介紹核心代碼和核心流程,類型判斷和express的使用等不包括在內。java

express

express裏的核心文件是index、express、application、router/index、router/layer、router/route。 index裏只有一句話git

module.exports = require('./lib/express');
複製代碼

導入express,並導出。express文件裏是導出許多api,像express、express.Router等。咱們開發是用到的express(),其實是執行createApplication()。application裏是和app相關的api。 router/index裏是和router相關的代碼,router能夠理解成路由器,把各個請求發給route。咱們不會直接調用router/layer裏的方法,layer是一個抽象概念,在express裏中間件、路由都放在app._router.stack裏,stack裏的每一個元素就是一個layer。 route裏也有一個stack,裏面的元素也是layer。github

🌰

  • 從下面的代碼開始對express源碼的研究:
  • 用express作一個簡單的服務器,訪問http://localhost:3000,返回"Hello World"
const express = require('express');
const app = express();
app.get('/', (req, res,next)=>{
    res.send('Hello World');
    next()
});
app.listen(3000,()=>{
    console.log('server is ok');
});
複製代碼
  • express(),實際調用createApplication(),返回一個app函數。
function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };
  //把proto的方法給app等初始化操做。
  return app;
}
複製代碼
  • 這個app上有"application.js"的導出對象proto上的全部方法。proto在"application.js"裏命名app,爲了方便,下文都成爲app。
  • app上有一個lazyrouter()方法,改方法主要是判斷app._router是否存在,若是不存在new Router賦值給app._router。
  • app.get裏的核心代碼以下:
app.get = function(path){
    this.lazyrouter();
    var route = this._router.route(path);
    route[method].apply(route, slice.call(arguments, 1));
    return this;
  };
複製代碼
  • route裏是真正處理請求回調的函數,在route[method]裏,循環參數,每次循環新建一個layer,handle是app.get的回調,把layer放在route的stack裏。route[method]裏的核心代碼是:
var layer = Layer('/', {}, handle);
 layer.method = method;
 this.methods[method] = true;
 this.stack.push(layer);
複製代碼
  • this._router即Router的實例。this._router.route(path)這個方法的核心代碼以下:
proto.route = function route(path) {
  var route = new Route(path);
  var layer = new Layer(path, {}, route.dispatch.bind(route));
  layer.route = route;
  this.stack.push(layer);
  return route;
};
複製代碼
  • route方法裏新建了一個Route和Layer,Layer的第三個參數handle,是express中間件執行的核心內容(我的想法,歡迎討論)。源碼中能夠看到,layer放到了this.stack,其實就是app._router.stack。 app._router.stack裏存放着中間件。最後返回route。app.get執行結束,下面是app.listen:
app.listen = function listen() {
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};
複製代碼
  • listen裏是監聽建立一個server,把參數傳給server.listen,createServer的回調是this。咱們從createApplication裏能夠看到,如今的app是一個函數,因此請求來了,執行app.handle。app.handle裏實際是執行了this._router.handle(req, res, done), express裏用到了不少代理模式。在router.handle裏,處理一些請求的url和params,調用內部的next方法,從router.stack裏找到和請求匹配的layer,最終調用layer.handle_request方法,並把next做爲參數傳入。 layer.handle_request裏調用this.handle,this.handle是Layer的第三個參數route.dispatch.bind(route)。在dispatch裏執行next找到stack裏的layer,執行layer.handle_request,並把next傳入。layer.handle_request執行handle,即app.get的回調函數。
  • 初始化
  • get方法
  • 發起請求

路由

在express裏建立路由主要由這幾種方法:express

  • app.METHODS
  • app.route().get().post()
  • app.all()
  • express.Router(),這個和上面的方法有一點不同。須要app.use(path,router)才能使用。下文會給出詳細的數據結構 這裏面的核心代碼是:
this.lazyrouter();
var route = this._router.route(path);
route[methods](fn);
複製代碼
  • express建立路由,其實是先調用_router.route(),再調用route.METHODS。
proto.route = function route(path) {
  var route = new Route(path);
  var layer = new Layer(path, {}, route.dispatch.bind(route));
  layer.route = route;
  this.stack.push(layer);
  return route;
};
Route.prototype[method] = function(){
    //把參數轉化成數組 handles
    for (var i = 0; i < handles.length; i++) {
      var handle = handles[i];
      var layer = Layer('/', {}, handle);
      layer.method = method;
      this.methods[method] = true;
      this.stack.push(layer);
    }
    return this;
  };
複製代碼
  • 從 layer.route = route;能夠得出路由是掛載layer上的。
//Route的數據結構
{
    methods:{},
    path:path,
    stack:[
        Layer{
        handle:handle
        method:method
        ...
        }
    ]
}
複製代碼

中間件

  • 中間件分爲:應用級中間件、路由級中間件、錯誤處理中間件、內置中間件、第三方中間件。
  • 錯誤處理中間件和其餘中間件的區別是回調函數有四個參數,第一個參數是錯誤對象。
  • 中間件的使用有兩種:掛載在app、掛載在express.Router()。
  • app.use裏最終調用router.use,router.use的核心代碼:
var layer = new Layer(path, {}, fn);
layer.route = undefined;
this.stack.push(layer); //app._router.stack.push(layer)
複製代碼
  • app.use和app.METHOD,建立的中間件的數據結構是不同的。
//app.use建立的layer
Layer{
    route:undefined,
    handle:fn
}
//app.get建立的layer
Layer{
    route:route,
    handle:route.dispatch.bind(route)
}
複製代碼
  • 用app.use調用,一個用express.Router()建立的路由,即app.use(router),數據結構變爲:
Layer{
    route:undefined,
    handle:router
}
複製代碼
  • 若是路由中間件調用路由中間件,router.use(router.use(router.get(path))),最終被app.use(router)執行。流程圖以下
  • express中間件能夠抽象成下面的樣子
  • Router的實例是一個完整的中間件和路由系統,所以常稱其爲一個 「mini-app」。app的use和定義路由方法不少都是經過Router實現的。
  • app、Router、Route、Layer的主要數據結構能夠用下圖表示

總結

  • 路由和中間件是express的核心,學會路由和中間件,再學express其餘相關的會事半功倍。
  • 閱讀源碼,不只知道了express的原理,還從代碼中學到了用代理模式的優勢,一處實現,多處調用,職責單一,改動小。
  • 從使用api的角度一步步"解刨"源碼,推導出做者的思想及數據結構。若是咱們從做者的思想及數據結構,和api的設計,反推出源碼的實現,可能閱讀源碼時效率會更高。

參考

相關文章
相關標籤/搜索