看了慕課網雙越老師的課以後結合本身的理解作了一些簡單的總結,若有不恰當之處,歡迎指正。node
提到 express
就不得不提到中間件,接下來就簡單的介紹一下 expres
中間件的簡單應用與部分經常使用函數的實現。express
在平常項目的開發中,登陸驗證是一個很是常見的場景,這個時候 express
中間件就能夠派上用場了。接下來分別使用原生 node
和 express
中間件的方法實現簡單的登陸驗證。json
應用場景:在獲取博客列表以前要進行登陸驗證,只有在登陸狀態下才能夠獲取博客列表。api
原生 node.js
實現登陸驗證:數組
if(method === 'GET' && req.path === '/api/blog/list') { // 若req.session.username有值說明已經登陸 if (req.session.username){ // 登陸狀態下能夠獲取博客列表 獲取博客列表 } }
express
中間件實現登陸驗證:服務器
function loginCheck(req, res, next) { if (某個登陸成功的條件) { next(); // 登陸成功執行next } else { res.json({ errno: -1, msg: 'login fail' }) } } // 若在登陸狀態下,loginCheck會執行next函數,從而執行第二個中間件,獲取博客列表。 app.get('/api/blog/list', loginCheck, (req, res, next) => { 得到博客列表 })
雖然上面這個簡單的例子中看上去彷佛原生 node.js
要更簡單一些,可是使用中間件的方式封裝性更好,在一些比價複雜的項目中就能體現出優點了。cookie
use, get, post, next, listen
等方法的實現結合上面的流程圖和最後給出的完整代碼,簡單說明一下實現的過程:session
這裏以 use
(在實現思路上和 get
, post
是同樣的)爲例說明:app
獲取路徑和中間件函數
use
將用戶調用時傳入的參數傳給 register
函數,register
函數將獲取到參數後分離出路徑和中間兩部分,路徑存放在 info.path
中,中間件存放在 info.stack
中。而後 register
函數將分離開的路徑和中間件再返回給 use
,use
拿到分離後的路徑和中間件以後,會將其賦值給 constructor
函數中 this.routes.all
。
注意:register 在分離路徑時須要注意的一點是對於沒有路徑只有中間件的參數會將其路徑賦值爲根路徑
/
listen
方法的實現
使用 express
時調用 app.listen(3000)
實際上不僅是監聽 3000
端口還建立了一個 http
服務,參數是一個回調函數,代碼以下:
listen(...args) { const server = http.createServer(this.callback()); server.listen(...args); }
res.json
方法的實現
在回調函數 callback
中首先實現一個 res.json
方法,實現方式也比較簡單。
res
的 Header
的 Content-type
爲 application/json
,res.end({JSON.stringify(data)})
將用戶傳入的 data
對象轉換爲 JSON
字符串輸出到屏幕上。獲取須要執行的中間件
這部分經過 match
函數實現。經過 req.url
和 req.method.toLowerCase
來獲取須要執行的中間件。中間實現的方法就有不少了,思路就是經過method
找出相應的路徑和中間件,這個已經在 this.routes
中分類存儲了。而後找出路徑和中間件以後利用 indexOf
方法,
if (url.indexOf(routeInfo.path) === 0)
若是 if
判斷成立說明知足條件,那麼須要執行的中間件也就找出來了。
app.next
方法的實現
這部分經過 handle
函數實現。思路:定義一個 next
函數,在那些須要執行的中間件中,先取出第一個,若是不爲空那麼開始執行,三個參數分別爲 req, res, next
。後面須要執行的中間件到底執不執行取決於前一箇中間件的程序中是否調用了 next
方法。代碼以下:
const next = () => { // shift 取得數組的第一項 const middleware = stack.shift(); if (middleware) { // 執行中間件函數 middleware(req, res, next); } }; // 定義完後當即執行 next();
注意:在定義完next函數以後,必須當即執行一次next函數,不然第一個須要執行的中間件也不會執行了。
中間件中傳入的參數中的next,就是在這裏定義的next函數,若是前一箇中間件程序中執行了next函數,那麼會再執行
stack.shift
取出須要執行的中間件中的下一個中間件。
附上完整代碼:
const http = require('http'); const slice = Array.prototype.slice; class LikeExpress { constructor() { // 存放路徑和中間件,all中存放的是調用use傳入的路徑和中間件 this.routes = { all: [], get: [], post: [] } } // 分離出路徑和中間件,info.path中存放路徑,info.stack 中存放中間件 register(path) { const info = {}; if (typeof path === 'string') { info.path = path; // info.stack 存放全部的中間件 // 若是第一個參數是路由在取中間件時就要從數組的第2個位置開始取 // slice.call(arguments, 1) 的做用就是取arguments從第二個位置開始以後的全部元素都取出來並變成數組的形式。 info.stack = slice.call(arguments, 1); } else { // 若是第一個參數不是一個路由,那麼咱們就假定第一個參數是一個根路由 info.path = '/'; info.stack = slice.call(arguments, 0); } return info; } use() { // 實際使用時,參數是經過use傳遞進來的 // 將全部的參數傳入到register函數中 const info = this.register.apply(this, arguments); // info 是一個對象,info.path 中存放的是路徑,info.stack 中存放的是中間件 this.routes.all.push(info); } get() { const info = this.register.apply(this, arguments); this.routes.get.push(info); } post() { const info = this.register.apply(this, arguments); this.routes.post.push(info); } // 匹配use,get或post方法會執行的中間件 match(method, url) { let stack = []; if (url === '/favicon.ico') { return stack; } // 獲取routes let curRoutes = []; // concat 是數組中的一個方法,若是沒有參數,那麼會生成一個當前數組的副本並將其賦值給前面的變量,若是有參數會將參數加入到生成的副本的後面而後將其賦值給變量 // 若是是use,那麼就把use中的路徑和中間列表複製到curRoutes中 // 若是方法是get或post那麼下面這句話,因爲this.routes.all是undefined,因此會將當前curRoutes生成一個副本賦值給curRoutes,其實一點變化都沒有 curRoutes = curRoutes.concat(this.routes.all); // 若是是get或post,那麼就把相應的路徑和中間件複製到curRoutes中 curRoutes = curRoutes.concat(this.routes[method]); curRoutes.forEach(routeInfo => { // url='/api/get-cookie' routeInfo.path='/' // url='/api/get-cookie' routeInfo.path='/api' // url='api/get-cookie' routeInfo.path='/api/get-cookie' if (url.indexOf(routeInfo.path) === 0) { // 匹配成功 stack = stack.concat(routeInfo.stack); } }); return stack; } // 核心的 next 機制 handle(req, res, stack) { const next = () => { // 拿到第一個匹配的中間件 // shift 取得數組的第一項 const middleware = stack.shift(); if (middleware) { // 執行中間件函數 middleware(req, res, next); } }; // 定義完後當即執行 next(); } // callback是一個(req, res) => {} 的函數,結合http-test中的代碼去理解 callback() { return (req, res) => { // res.json 是一個函數,在express中使用時傳入一個對象便可在屏幕中顯示出來,這裏實現了這個功能 res.json = (data) => { res.setHeader('Content-type', 'application/json'); res.end( JSON.stringify(data) ); }; const url = req.url; const method = req.method.toLowerCase(); // 找到須要執行的中間件(經過路徑和method,有可能一個須要執行的中間件也沒有) const resultList = this.match(method, url); this.handle(req, res, resultList); } } // express 中listen的做用不單單是監聽端口,還要建立服務器 listen(...args) { const server = http.createServer(this.callback()); server.listen(...args); } } module.exports = () => { return new LikeExpress() };
完