成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

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

一、首先安裝express

二、創建example.js文件

       創建myExpress.js文件

       實現app.get()方法

       實現post等其他方法

       實現app.all方法

       中間件app.use的實現

       什么是錯誤中間件?

       學習總結

一、首先安裝express

  1. npm install express 

安裝express是為了示范。

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

二、創建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. }) 

如代碼所示,執行node example.js就運行起了一個服務器。

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

好了,現在開始實現我們的express吧!

創建myExpress.js文件

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

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

我們可以初步實現express如下:

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

在上面代碼中,發現app有listen方法。

因此我們可以進一步給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實現的是創建一個服務器,并且將服務器綁定到某個端口運行起來。

因此可以這樣完善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; 

這里可能會有同學有所疑問,為什么 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改成一個對象,也是沒有問題的。

實現app.get()方法

app.get方法接受兩個參數,路徑和回調函數。

  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方法執行的時候,將接收到的兩個參數:路徑和方法,包裝成一個對象push到routes里了。

可想而知,當我們在瀏覽器輸入路徑的時候,肯定會執行http.createServer里的回調函數。

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

然后遍歷循環routes,尋找對應的路由,執行回調方法。如下面代碼所示。

  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.找到對應的路由,執行回調方法 
  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; 

運行一下代碼。

可見運行成功:

實現post等其他方法。

很簡單,我們可以直接復制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; 

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

// 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是一個方法數組。如下面所示的數組:

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

遍歷方法數組,就可以實現所有方法了。

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

實現app.all方法

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

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

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

實現all方法也非常簡單,如下代碼所示:

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

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

  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.找到對應的路由,執行回調方法 
  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的實現

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

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

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

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

所以我們必須實現next這個方法。

其實可以猜想,next應該就是一個瘋狂調用自己的方法。也就是遞歸。

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

實際上,不管是app.use還說app.all還是app.get。其實都是把layer放進routes里,然后再統一遍歷routes來判斷該不該執行layer里的handler方法。可以看下next方法的實現。

  1. function next() { 
  2.     // 已經迭代完整個數組,還是沒有找到匹配的路徑 
  3.     if (index === app.routes.length) return res.end('Cannot find '
  4.     let { method, path, handler } = app.routes[index++] // 每次調用next就去下一個layer 
  5.     if (method === 'middle') { // 處理中間件 
  6.         if (path === '/' || path === pathname || pathname.starWidth(path + '/')) { 
  7.             handler(req, res, next
  8.         } else { // 繼續遍歷 
  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數組。

而且我們可以發現,如果是使用中間件的話,那么只要path是“/”或者前綴匹配,這個中間件就會執行。由于handler會用到參數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.找到對應的路由,執行回調方法 
  44.             function next() { 
  45.                 // 已經迭代完整個數組,還是沒有找到匹配的路徑 
  46.                 if (index === app.routes.length) return res.end('Cannot find '
  47.                 let { method, path, handler } = app.routes[index++] // 每次調用next就去下一個layer 
  48.                 if (method === 'middle') { // 處理中間件 
  49.                     if (path === '/' || path === pathname || pathname.starWidth(path + '/')) { 
  50.                         handler(req, res, next
  51.                     } else { // 繼續遍歷 
  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; 

當我們請求路徑就會發現中間件確實執行成功。

不過,這里的中間價實現還不夠完美。

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

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

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

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

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

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

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

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

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

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

什么是錯誤中間件?

錯誤處理中間件函數的定義方式與其他中間件函數基本相同,差別在于錯誤處理函數有四個自變量而不是三個,專門具有特征符 (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. }); 

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

舉個例子:

如圖所示,當第一個中間件往next傳遞參數的時候,表示執行出現了錯誤。然后就會跳過其他陸游和中間件和路由,直接執行錯誤中間件。當然,執行完錯誤中間件,就會繼續執行后面的中間件。

例如:

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

那原理該怎么實現呢?

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

  1. function next(err) { 
  2.     // 已經迭代完整個數組,還是沒有找到匹配的路徑 
  3.     if (index === app.routes.length) return res.end('Cannot find '
  4.     let { method, path, handler } = app.routes[index++] // 每次調用next就去下一個layer 
  5.     if( err ){ // 如果有錯誤,應該尋找中間件執行。 
  6.         if(handler.length === 4) { //找到錯誤中間件 
  7.             handler(err,req,res,next
  8.         }else { // 繼續徐州 
  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 { // 繼續遍歷 
  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有沒有值,就可以判斷需不需要查找錯誤中間件來執行了。

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

到此,express框架的實現就大功告成了。

學習總結

通過這次express手寫原理的實現,更加深入地了解了express的使用,發現:

  • 中間件和路由都是push進一個routes數組里的。
  • 當執行中間件的時候,會傳遞next,使得下一個中間件或者路由得以執行。
  • 當執行到路由的時候就不會傳遞next,也使得routes的遍歷提前結束。
  • 當執行完錯誤中間件后,后面的中間件或者路由還是會執行的。

 

責任編輯:武曉燕 來源: 前端陽光
相關推薦

2020-11-24 07:48:32

React

2020-10-20 09:12:57

axios核心原理

2022-08-27 13:49:36

ES7promiseresolve

2020-10-23 09:26:57

React-Redux

2021-05-08 07:53:33

面試線程池系統

2022-04-01 07:52:42

JavaScript防抖節流

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

2024-03-05 10:33:39

AOPSpring編程

2024-08-22 10:39:50

@Async注解代理

2025-03-07 00:00:10

2020-12-09 10:29:53

SSH加密數據安全

2020-11-02 09:35:04

ReactHook

2021-12-02 08:19:06

MVCC面試數據庫

2020-12-03 08:14:45

Axios核心Promise

2024-02-29 16:49:20

volatileJava并發編程

2024-11-19 15:13:02

2023-12-27 18:16:39

MVCC隔離級別幻讀
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 午夜精品久久久久久久星辰影院 | 欧美一区二区综合 | 久久天堂网 | 久久激情五月丁香伊人 | 激情国产视频 | 国产精品免费一区二区三区四区 | 久久久久久久久久久久久9999 | 久久久精品国产 | 1区2区视频 | 久久久片| 视频在线观看一区 | 在线日韩中文字幕 | 激情综合五月 | 午夜私人影院 | 乳色吐息在线观看 | 91免费视频 | 韩日三级 | 国产美女黄色片 | 午夜精品影院 | 欧美日韩一区精品 | aaa天堂| 亚洲国产高清高潮精品美女 | 久久毛片| 青青草av网站 | 精精国产xxxx视频在线 | 亚洲人一区 | 黄色三级免费网站 | 国产精品自产拍 | 国产精品久久久久久久久久久久冷 | 国产情侣激情 | 日本精品视频 | 91xx在线观看 | 欧美激情黄色 | 国产精品网址 | 羞羞视频网站免费观看 | 亚洲国产精品视频 | 一区二区三区欧美在线 | 色播99| 欧美性生交大片免费 | 91伊人| 秋霞在线一区二区 |