I don't read books, never went to school, I just read other people's code and always wonder how things work. ——TJ Holowaychuk
簡介
這篇文章的主要的目的是通過研究express核心原始碼,讓我們對express深入理解,不僅會用express,還要理解其背後的思想,提高開發效率。研究express原始碼,學習大神的程式碼結構。本文只介紹核心程式碼和核心流程,型別判斷和express的使用等不包括在內。
express
express裡的核心檔案是index、express、application、router/index、router/layer、router/route。 index裡只有一句話
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。
?
- 從下面的程式碼開始對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裡建立路由主要由這幾種方法:
- 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的設計,反推出原始碼的實現,可能閱讀原始碼時效率會更高。