咱們在開發的過程當中,或多或少會用到Node.js,好比用Node.js在本地起一個靜態文件服務器等。可是Node.js 的 API 對開發者來講並非很是友好。例如,若是咱們想從服務器發送一個 JPEG 圖片的話,可能須要至少 四五 行代碼才行。建立可複用 HTML 模版則更復雜。另外,Node.js 的 HTTP 模塊雖然強大,可是仍然缺乏一些實用特性。 Express 的出現就是爲了解決這些問題,讓咱們可以高效的使用 Node.js 來編寫 Web 應用。javascript
從大的方面來講,Express 爲 Node.js 的 HTTP 模塊帶來了兩大特性:html
下面咱們說幾個express的幾個經常使用api:java
根據請求路徑來處理客戶端發出的GET等各類請求。第一個參數path爲請求的路徑, 第二個參數爲處理請求的回調函數。git
let express = require('express');
let app = express();
app.listen(8080, () => {
console.log('started success');
});
app.get('/', function (req, res) {
res.end('ok');
});
複製代碼
中間件就是處理HTTP請求的函數,用來完成各類特定的任務,好比檢查用戶是否登陸、檢測用戶是否有權限訪問等。github
app.use中放入的函數稱爲中間件函數,通常有三個特色:express
const express = require('../lib/express');
const app = express();
app.use(function (req, res, next) {
console.log('Ware1:', Date.now());
next('wrong');
});
app.get('/', function (req, res, next) {
res.end('1');
});
const user = express.Router();
user.use(function (req, res, next) {
console.log('Ware2', Date.now());
next();
});
user.use('/2', function (req, res, next) {
res.end('2');
});
app.use('/user', user);
app.use(function (err, req, res, next) {
res.end('catch ' + err);
});
app.listen(3000, function () {
console.log('server started at port 3000');
});
複製代碼
監聽客戶端向服務器發送請求的函數api
批量處理相同參數數組
const express = require('../lib/express');
const app = express();
app.param('uid',function(req,res,next,val,name){
req.user = {id:1,name:'Lucy'};
console.log('1');
next();
})
app.param('uid',function(req,res,next,val,name){
req.user.name = 'Tom';
next();
})
app.get('/user/:uid',function(req,res){
console.log(req.user);
res.end('user');
});
app.listen(3000);
複製代碼
設置參數,好比渲染模板的時候咱們會常用到。bash
app.set('views', path.resolve(path.join(__dirname, 'views')));
app.set('view engine', 'html');
複製代碼
規定何種文件用何種方法來渲染服務器
app.engine('html', html);
複製代碼
簡單的介紹了集中經常使用api的用法,接下來就要開始進入主題了,那就是根據express源碼,模擬express框架,實現上述的集中api。
本次模擬實現的api有app.get()
、app.use()
、app.listen()
、app.param()
、 app.render()
、app.set()
、app.engine()
。
項目結構以下:
lib/
|
| - middle/
| | - init.js 內置中間件
|
| - route/
| | - index.js 路由系統
| | - layer.js 層
| | - route.js 路由
|
| - application.js 應用
| - html.js 模板引擎
| - express.js 入口
|
test/ 這裏放入的是測試用例
|
複製代碼
接下來咱們一一介紹一下express的實現邏輯。由於express都是經過app來操做的,即express.js文件是express的入口,express.js的代碼實現很簡單,就是導出一個Application的實例。express把主要的方法放在Application上面了,咱們先來張Application的概覽圖,來直觀的感覺下,以下圖:
Application上的屬性
爲了便於描述咱們將Application
的實例稱爲app
(下同)。app
是實現express功能的入口,順着圖中第一個箭頭的方向,app._router
屬性指向一個Router
的實例(灰色背景部分),app._router
是一個路由系統,這個路由系統中會管理客戶端發來請求的回調函數的執行。Router
上的屬性也位於左側的一欄文字中,咱們先來解釋一下屬性(一樣的,黑色部分爲實例上的屬性,紅色加粗部分爲原型上的屬性)。
Router上的屬性
Layer
的實例(Layer
下面接着會介紹)在咱們處理客戶端發來請求的回調函數的過程當中,主要靠的是循環app._router.stack
中的每一層(如圖中的layer一、layer二、layer3)來實現,那麼每一層究竟是什麼呢?我我的根據處理的邏輯把layer作了一個分類,包括三類:路由層、中間件層、具備子路由系統的中間件層。咱們詳細介紹一下這三個類:
Layer上的屬性
/user/getlist
Route
的實例key
組成的數組path
是否和請求的url地址匹配this.handler
屬性對應的方法next()
函數傳來的參數路由層是經過app.get(path, handler)
訂閱的,該層會經過app._router.stack.push()
放入到app._router.stack
中,app._router.stack
是一個數組,存放的是各類層(layer),包括後面的中間件層也會放到app._router.stack
中。須要注意的是路由層的route屬性指向是一個Route
的實例,而且在new Layer
的時候將Route
的實例上的dispatch
方法做爲第二個參數傳遞給Layer
,以下代碼:
let route = new Route(path);
let layer = new Layer(path, route.dispatch.bind(route));
layer.route = route;
this.stack.push(layer);
複製代碼
須要注意的是路由層的route
屬性指向一個Route
的實例。
Route的屬性
app.get(path, handlers)
中handlers中的單個handler。app._router.stack
中的layer的handler屬性指向的是route.dispatch.bind(route)
stack
中的方法的集合[method]
方法最終是派發給route
中來實現,因此這個方法就是將派發來的[method]
方法push
到stack
中app.use(path, handler)
訂閱的,該層也會放入到
app._router.stack
中。須要注意的是該層的
route
屬性爲
undefined
。
app._router
所屬的類是同一個類,因此這個子路由系統跟
app._router
具備相同的屬性和相同的原型上的方法。這一層也是經過
app.use()
訂閱的,可是稍有不一樣,以下代碼:
//中間件層 的訂閱方式
app.use('/', function (req, res, next) {
console.log('Ware1:', Date.now());
next('wrong');
});
// 具備子路由系統的中間件層 的訂閱方式
const user = express.Router();
user.use(function (req, res, next) {
console.log('Ware2', Date.now());
next();
});
user.use('/2', function (req, res, next) {
res.end('2');
});
複製代碼
當請求函數走到這一層的時候,this.handler
執行時會進入到圖中箭頭指向的灰色背景部分,即子路由系統,這個子路由系統中的stack也是存放的是子路由系統中訂閱的函數。
介紹了這麼多,到底這些Router
、Layer
、Route
等是如何配合工做的?下面咱們詳細介紹一下。
當客戶端發起請求的時候app就會派發給_router.handle
執行,_router.handle
的邏輯就是把訂閱在_router.stack
中的handler依次執行,以下圖:
接下來我把_router.stack
裏面每個layer時的執行書序邏輯圖抽離出來,以下圖:
express還有一個功能就是能夠實現模板引擎,實現的代碼邏輯以下:
let head = "let tpl = ``;\nwith (obj) {\n tpl+=`";
str = str.replace(/<%=([\s\S]+?)%>/g, function () {
return "${" + arguments[1] + "}";
});
str = str.replace(/<%([\s\S]+?)%>/g, function () {
return "`;\n" + arguments[1] + "\n;tpl+=`";
});
let tail = "`}\n return tpl; ";
let html = head + str + tail;
let fn = new Function('obj', html);
let result = fn(options);
複製代碼
寫到這裏,express框架的經常使用api已經介紹完了,本文只是介紹了實現邏輯,具體的項目代碼以及測試用例請參見個人GitHub。
參考文獻