自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

手寫Express核心原理,再也不怕面試官問我Express原理

開發(fā) 前端
我們可以知道,express得到的是一個方法,然后方法執(zhí)行后得到了app。而app實際上也是一個函數(shù),至于為什么會是函數(shù),我們下面會揭秘。
本文轉(zhuǎn)載自微信公眾號「前端陽光」,作者事業(yè)有成的張啦啦 。轉(zhuǎn)載本文請聯(lián)系前端陽光公眾號。

一、首先安裝express

二、創(chuàng)建example.js文件

       創(chuàng)建myExpress.js文件

       實現(xiàn)app.get()方法

       實現(xiàn)post等其他方法

       實現(xiàn)app.all方法

       中間件app.use的實現(xiàn)

       什么是錯誤中間件?

       學(xué)習(xí)總結(jié)

一、首先安裝express

  1. npm install express 

安裝express是為了示范。

已經(jīng)把代碼放到github:https://github.com/Sunny-lucking/HowToBuildMyExpress ??梢皂樖纸o個star嗎?謝謝大佬們。

二、創(chuàng)建example.js文件

  1. // example.js 
  2. const express = require('express'
  3. const app = express() 
  4. const port = 3000 
  5.  
  6. app.get('/', (req, res) => { 
  7.   res.send('Hello World!'
  8. }) 
  9.  
  10. app.listen(port, () => { 
  11.   console.log(`Example app listening at http://localhost:${port}`) 
  12. }) 

如代碼所示,執(zhí)行node example.js就運行起了一個服務(wù)器。

如下圖所示,現(xiàn)在我們決定創(chuàng)建一個屬于我們的express文件,引入的express改成引入我們手寫的express。

好了,現(xiàn)在開始實現(xiàn)我們的express吧!

創(chuàng)建myExpress.js文件

  1. const express = require('express'
  2. const app = express() 

由 這兩句代碼,我們可以知道,express得到的是一個方法,然后方法執(zhí)行后得到了app。而app實際上也是一個函數(shù),至于為什么會是函數(shù),我們下面會揭秘。

我們可以初步實現(xiàn)express如下:

  1. // myExpress.js 
  2. function createApplication() { 
  3.     let app = function (req,res) { 
  4.  
  5.     } 
  6.     return app; 
  7.  
  8. module.exports = createApplication; 

在上面代碼中,發(fā)現(xiàn)app有l(wèi)isten方法。

因此我們可以進(jìn)一步給app添加listen方法:

  1. // myExpress.js 
  2. function createApplication() { 
  3.     let app = function (req,res) { 
  4.  
  5.     } 
  6.     app.listen = function () { 
  7.  
  8.     } 
  9.     return app; 
  10.  
  11. module.exports = createApplication; 

app.listen實現(xiàn)的是創(chuàng)建一個服務(wù)器,并且將服務(wù)器綁定到某個端口運行起來。

因此可以這樣完善listen方法。

  1. // myExpress.js 
  2. let http = require('http'); 
  3. function createApplication() { 
  4.     let app = function (req,res) { 
  5.         res.end('hahha'); 
  6.     } 
  7.     app.listen = function () { 
  8.         let server = http.createServer(app) 
  9.         server.listen(...arguments); 
  10.  
  11.     } 
  12.     return app; 
  13.  
  14. module.exports = createApplication; 

這里可能會有同學(xué)有所疑問,為什么 http.createServer(app)這里要傳入app。

其實我們不傳入app,也就是說,讓app不是一個方法,也是可以的。

我們可以改成這樣。

  1. // myExpress.js 
  2. let http = require('http'); 
  3. function createApplication() { 
  4.     let app = {}; 
  5.  
  6.     app.listen = function () { 
  7.         let server = http.createServer(function (req, res) { 
  8.             res.end('hahha'
  9.         }) 
  10.         server.listen(...arguments); 
  11.  
  12.     } 
  13.     return app; 
  14.  
  15. module.exports = createApplication; 

如代碼所示,我們將app改成一個對象,也是沒有問題的。

實現(xiàn)app.get()方法

app.get方法接受兩個參數(shù),路徑和回調(diào)函數(shù)。

  1. // myExpress.js 
  2. let http = require('http'); 
  3. function createApplication() { 
  4.     let app = {}; 
  5.     app.routes = [] 
  6.     app.get = function (path, handler) { 
  7.         let layer = { 
  8.             method: 'get'
  9.             path, 
  10.             handler 
  11.         } 
  12.         app.routes.push(layer) 
  13.     } 
  14.     app.listen = function () { 
  15.         let server = http.createServer(function (req, res) { 
  16.              
  17.             res.end('hahha'
  18.         }) 
  19.         server.listen(...arguments); 
  20.     } 
  21.     return app; 
  22.  
  23. module.exports = createApplication; 

如上面代碼所示,給app添加了route對象,然后get方法執(zhí)行的時候,將接收到的兩個參數(shù):路徑和方法,包裝成一個對象push到routes里了。

可想而知,當(dāng)我們在瀏覽器輸入路徑的時候,肯定會執(zhí)行http.createServer里的回調(diào)函數(shù)。

所以,我們需要在這里 獲得瀏覽器的請求路徑。解析得到路徑。

然后遍歷循環(huán)routes,尋找對應(yīng)的路由,執(zhí)行回調(diào)方法。如下面代碼所示。

  1. // myExpress.js 
  2. let http = require('http'); 
  3. const url  = require('url'); 
  4. function createApplication() { 
  5.     let app = {}; 
  6.     app.routes = [] 
  7.     app.get = function (path, handler) { 
  8.         let layer = { 
  9.             method: 'get'
  10.             path, 
  11.             handler 
  12.         } 
  13.         app.routes.push(layer) 
  14.     } 
  15.     app.listen = function () { 
  16.         let server = http.createServer(function (req, res) { 
  17.             // 取出layer  
  18.             // 1. 獲取請求的方法 
  19.             let m = req.method.toLocaleLowerCase(); 
  20.             let { pathname } = url.parse(req.url, true); 
  21.              
  22.             // 2.找到對應(yīng)的路由,執(zhí)行回調(diào)方法 
  23.             for (let i = 0 ; i< app.routes.length; i++){ 
  24.                 let {method,path,handler} = app.routes[i] 
  25.                 if (method === m && path === pathname ) { 
  26.                     handler(req,res); 
  27.                 } 
  28.             } 
  29.             res.end('hahha'
  30.         }) 
  31.         server.listen(...arguments); 
  32.     } 
  33.     return app; 
  34.  
  35. module.exports = createApplication; 

運行一下代碼。

可見運行成功:

實現(xiàn)post等其他方法。

很簡單,我們可以直接復(fù)制app.get方法,然后將method的值改成post就好了。

  1. // myExpress.js 
  2. let http = require('http'); 
  3. const url  = require('url'); 
  4. function createApplication() { 
  5.     。。。 
  6.     app.get = function (path, handler) { 
  7.         let layer = { 
  8.             method: 'get'
  9.             path, 
  10.             handler 
  11.         } 
  12.         app.routes.push(layer) 
  13.     } 
  14.     app.post = function (path, handler) { 
  15.         let layer = { 
  16.             method: 'post'
  17.             path, 
  18.             handler 
  19.         } 
  20.         app.routes.push(layer) 
  21.     } 
  22.     。。。 
  23.     return app; 
  24.  
  25. module.exports = createApplication; 

這樣是可以實現(xiàn),但是除了post和get,還有其他方法啊,難道每一個我們都要這樣寫嘛?,當(dāng)然不是,有個很簡單的方法。

// myExpress.js

  1. function createApplication() { 
  2.     ...  
  3.     http.METHODS.forEach(method => { 
  4.         method = method.toLocaleLowerCase() 
  5.         app[method] = function (path, handler) { 
  6.             let layer = { 
  7.                 method, 
  8.                 path, 
  9.                 handler 
  10.             } 
  11.             app.routes.push(layer) 
  12.         } 
  13.     }); 
  14.     ... 
  15.  
  16. module.exports = createApplication; 

如代碼所示,http.METHODS是一個方法數(shù)組。如下面所示的數(shù)組:

  1. ["GET","POST","DELETE","PUT"]。 

遍歷方法數(shù)組,就可以實現(xiàn)所有方法了。

測試跑了一下,確實成功。

實現(xiàn)app.all方法

all表示的是匹配所有的方法,

app.all('/user')表示匹配所有路徑是/user的路由

app.all('*')表示匹配任何路徑 任何方法 的 路由

實現(xiàn)all方法也非常簡單,如下代碼所示:

  1. app.all = function (path, handler){ 
  2.         let layer = { 
  3.             method: "all"
  4.             path, 
  5.             handler 
  6.         } 
  7.         app.routes.push(layer) 
  8.     } 

然后只需要續(xù)改下路由器匹配的邏輯,如下代碼所示,只需要修改下判斷。

  1. app.listen = function () { 
  2.     let server = http.createServer(function (req, res) { 
  3.         // 取出layer  
  4.         // 1. 獲取請求的方法 
  5.         let m = req.method.toLocaleLowerCase(); 
  6.         let { pathname } = url.parse(req.url, true); 
  7.  
  8.         // 2.找到對應(yīng)的路由,執(zhí)行回調(diào)方法 
  9.         for (let i = 0 ; i< app.routes.length; i++){ 
  10.             let {method,path,handler} = app.routes[i] 
  11.             if ((method === m || method === 'all') && (path === pathname || path === "*")) { 
  12.                 handler(req,res); 
  13.             } 
  14.         } 
  15.         console.log(app.routes); 
  16.         res.end('hahha'
  17.     }) 
  18.     server.listen(...arguments); 

可見成功。

中間件app.use的實現(xiàn)

這個方法的實現(xiàn),跟其他方法差不多,如代碼所示。

  1. app.use = function (path, handler) { 
  2.     let layer = { 
  3.         method: "middle"
  4.         path, 
  5.         handler 
  6.     } 
  7.     app.routes.push(layer) 

但問題來了,使用中間件的時候,我們會使用next方法,來讓程序繼續(xù)往下執(zhí)行,那它是怎么執(zhí)行的。

  1. app.use(function (req, res, next) { 
  2.   console.log('Time:'Date.now()); 
  3.   next(); 
  4. }); 

所以我們必須實現(xiàn)next這個方法。

其實可以猜想,next應(yīng)該就是一個瘋狂調(diào)用自己的方法。也就是遞歸。

而且每遞歸一次,就把被push到routes里的handler拿出來執(zhí)行。

實際上,不管是app.use還說app.all還是app.get。其實都是把layer放進(jìn)routes里,然后再統(tǒng)一遍歷routes來判斷該不該執(zhí)行l(wèi)ayer里的handler方法。可以看下next方法的實現(xiàn)。

  1. function next() { 
  2.     // 已經(jīng)迭代完整個數(shù)組,還是沒有找到匹配的路徑 
  3.     if (index === app.routes.length) return res.end('Cannot find '
  4.     let { method, path, handler } = app.routes[index++] // 每次調(diào)用next就去下一個layer 
  5.     if (method === 'middle') { // 處理中間件 
  6.         if (path === '/' || path === pathname || pathname.starWidth(path + '/')) { 
  7.             handler(req, res, next
  8.         } else { // 繼續(xù)遍歷 
  9.             next(); 
  10.         } 
  11.     } else { // 處理路由 
  12.         if ((method === m || method === 'all') && (path === pathname || path === "*")) { 
  13.             handler(req, res); 
  14.         } else { 
  15.             next(); 
  16.         } 
  17.     } 

可以看到是遞歸方法的遍歷routes數(shù)組。

而且我們可以發(fā)現(xiàn),如果是使用中間件的話,那么只要path是“/”或者前綴匹配,這個中間件就會執(zhí)行。由于handler會用到參數(shù)req和res。所以這個next方法要在 listen里面定義。

如下代碼所示:

  1. // myExpress.js 
  2. let http = require('http'); 
  3. const url = require('url'); 
  4. function createApplication() { 
  5.     let app = {}; 
  6.     app.routes = []; 
  7.     let index = 0; 
  8.  
  9.     app.use = function (path, handler) { 
  10.         let layer = { 
  11.             method: "middle"
  12.             path, 
  13.             handler 
  14.         } 
  15.         app.routes.push(layer) 
  16.     } 
  17.     app.all = function (path, handler) { 
  18.         let layer = { 
  19.             method: "all"
  20.             path, 
  21.             handler 
  22.         } 
  23.         app.routes.push(layer) 
  24.     } 
  25.     http.METHODS.forEach(method => { 
  26.         method = method.toLocaleLowerCase() 
  27.         app[method] = function (path, handler) { 
  28.             let layer = { 
  29.                 method, 
  30.                 path, 
  31.                 handler 
  32.             } 
  33.             app.routes.push(layer) 
  34.         } 
  35.     }); 
  36.     app.listen = function () { 
  37.         let server = http.createServer(function (req, res) { 
  38.             // 取出layer  
  39.             // 1. 獲取請求的方法 
  40.             let m = req.method.toLocaleLowerCase(); 
  41.             let { pathname } = url.parse(req.url, true); 
  42.  
  43.             // 2.找到對應(yīng)的路由,執(zhí)行回調(diào)方法 
  44.             function next() { 
  45.                 // 已經(jīng)迭代完整個數(shù)組,還是沒有找到匹配的路徑 
  46.                 if (index === app.routes.length) return res.end('Cannot find '
  47.                 let { method, path, handler } = app.routes[index++] // 每次調(diào)用next就去下一個layer 
  48.                 if (method === 'middle') { // 處理中間件 
  49.                     if (path === '/' || path === pathname || pathname.starWidth(path + '/')) { 
  50.                         handler(req, res, next
  51.                     } else { // 繼續(xù)遍歷 
  52.                         next(); 
  53.                     } 
  54.                 } else { // 處理路由 
  55.                     if ((method === m || method === 'all') && (path === pathname || path === "*")) { 
  56.                         handler(req, res); 
  57.                     } else { 
  58.                         next(); 
  59.                     } 
  60.                 } 
  61.             } 
  62.  
  63.             next() 
  64.             res.end('hahha'
  65.         }) 
  66.         server.listen(...arguments); 
  67.     } 
  68.     return app; 
  69.  
  70. module.exports = createApplication; 

當(dāng)我們請求路徑就會發(fā)現(xiàn)中間件確實執(zhí)行成功。

不過,這里的中間價實現(xiàn)還不夠完美。

因為,我們使用中間件的時候,是可以不用傳遞路由的。例如:

  1. app.use((req,res) => { 
  2.   console.log("我是沒有路由的中間價"); 
  3. }) 

這也是可以使用的,那該怎么實現(xiàn)呢,其實非常簡單,判斷一下有沒有傳遞路徑就好了,沒有的話,就給個默認(rèn)路徑“/”,實現(xiàn)代碼如下:

  1. app.use = function (path, handler) { 
  2.     if(typeof path !== "string") { // 第一個參數(shù)不是字符串,說明不是路徑,而是方法 
  3.         handler = path; 
  4.         path = "/" 
  5.     } 
  6.     let layer = { 
  7.         method: "middle"
  8.         path, 
  9.         handler 
  10.     } 
  11.     app.routes.push(layer) 

看,是不是很巧妙,很容易。

我們試著訪問路徑“/middle”

咦?第一個中間件沒有執(zhí)行,為什么呢?

對了,使用中間件的時候,最后要執(zhí)行next(),才能交給下一個中間件或者路由執(zhí)行。

當(dāng)我們請求“/middle”路徑的時候,可以看到確實請求成功,中間件也成功執(zhí)行。說明我們的邏輯沒有問題。

實際上,中間件已經(jīng)完成了,但是別忘了,還有個錯誤中間件?

什么是錯誤中間件?

錯誤處理中間件函數(shù)的定義方式與其他中間件函數(shù)基本相同,差別在于錯誤處理函數(shù)有四個自變量而不是三個,專門具有特征符 (err, req, res, next):

  1. app.use(function(err, req, res, next) { 
  2.   console.error(err.stack); 
  3.   res.status(500).send('Something broke!'); 
  4. }); 

當(dāng)我們的在執(zhí)行next()方法的時候,如果拋出了錯誤,是會直接尋找錯誤中間件執(zhí)行的,而不會去執(zhí)行其他的中間件或者路由。

舉個例子:

如圖所示,當(dāng)?shù)谝粋€中間件往next傳遞參數(shù)的時候,表示執(zhí)行出現(xiàn)了錯誤。然后就會跳過其他陸游和中間件和路由,直接執(zhí)行錯誤中間件。當(dāng)然,執(zhí)行完錯誤中間件,就會繼續(xù)執(zhí)行后面的中間件。

例如:

如圖所示,錯誤中間件的后面那個是會執(zhí)行的。

那原理該怎么實現(xiàn)呢?

很簡單,直接看代碼解釋,只需在next里多加一層判斷即可:

  1. function next(err) { 
  2.     // 已經(jīng)迭代完整個數(shù)組,還是沒有找到匹配的路徑 
  3.     if (index === app.routes.length) return res.end('Cannot find '
  4.     let { method, path, handler } = app.routes[index++] // 每次調(diào)用next就去下一個layer 
  5.     if( err ){ // 如果有錯誤,應(yīng)該尋找中間件執(zhí)行。 
  6.         if(handler.length === 4) { //找到錯誤中間件 
  7.             handler(err,req,res,next
  8.         }else { // 繼續(xù)徐州 
  9.             next(err)  
  10.         } 
  11.     }else { 
  12.         if (method === 'middle') { // 處理中間件 
  13.             if (path === '/' || path === pathname || pathname.starWidth(path + '/')) { 
  14.                 handler(req, res, next
  15.             } else { // 繼續(xù)遍歷 
  16.                 next(); 
  17.             } 
  18.         } else { // 處理路由 
  19.             if ((method === m || method === 'all') && (path === pathname || path === "*")) { 
  20.                 handler(req, res); 
  21.             } else { 
  22.                 next(); 
  23.             } 
  24.         } 
  25.     } 

看代碼可見在next里判斷err有沒有值,就可以判斷需不需要查找錯誤中間件來執(zhí)行了。

如圖所示,請求/middle路徑,成功執(zhí)行。

到此,express框架的實現(xiàn)就大功告成了。

學(xué)習(xí)總結(jié)

通過這次express手寫原理的實現(xiàn),更加深入地了解了express的使用,發(fā)現(xiàn):

  • 中間件和路由都是push進(jìn)一個routes數(shù)組里的。
  • 當(dāng)執(zhí)行中間件的時候,會傳遞next,使得下一個中間件或者路由得以執(zhí)行。
  • 當(dāng)執(zhí)行到路由的時候就不會傳遞next,也使得routes的遍歷提前結(jié)束。
  • 當(dāng)執(zhí)行完錯誤中間件后,后面的中間件或者路由還是會執(zhí)行的。

 

責(zé)任編輯:武曉燕 來源: 前端陽光
相關(guān)推薦

2020-10-20 09:12:57

axios核心原理

2020-11-24 07:48:32

React

2022-08-27 13:49:36

ES7promiseresolve

2020-10-23 09:26:57

React-Redux

2022-04-01 07:52:42

JavaScript防抖節(jié)流

2021-05-08 07:53:33

面試線程池系統(tǒng)

2022-10-31 11:10:49

Javavolatile變量

2023-11-28 17:49:51

watch?computed?性能

2020-10-15 12:52:46

SpringbootJava編程語言

2021-04-22 07:49:51

Vue3Vue2.xVue3.x

2020-12-09 10:29:53

SSH加密數(shù)據(jù)安全

2024-08-22 10:39:50

@Async注解代理

2024-03-05 10:33:39

AOPSpring編程

2025-03-07 00:00:10

2020-12-03 08:14:45

Axios核心Promise

2021-12-02 08:19:06

MVCC面試數(shù)據(jù)庫

2020-11-02 09:35:04

ReactHook

2024-11-19 15:13:02

2025-04-16 00:00:01

JWT客戶端存儲加密令

2024-08-29 16:30:27

點贊
收藏

51CTO技術(shù)棧公眾號