<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 25339字,需瀏覽 51分鐘

           ·

          2021-08-12 17:49


          • 一、首先安裝express

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

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

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

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

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

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

          • 什么是錯(cuò)誤中間件?

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

          • 最后


          一、首先安裝express

          npm install express

          安裝express是為了示范。

          已經(jīng)把代碼放到github:https://github.com/Sunny-lucking/HowToBuildMyExpress 。可以順手給個(gè)star嗎?謝謝大佬們。

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

          // example.js
          const express = require('express')
          const app = express()
          const port = 3000

          app.get('/', (req, res) => {
            res.send('Hello World!')
          })

          app.listen(port, () => {
            console.log(`Example app listening at http://localhost:${port}`)
          })

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

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

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

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

          const express = require('express')
          const app = express()

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

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

          // myExpress.js
          function createApplication({
              let app = function (req,res{

              }
              return app;
          }

          module.exports = createApplication;

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

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

          // myExpress.js
          function createApplication({
              let app = function (req,res{

              }
              app.listen = function ({

              }
              return app;
          }

          module.exports = createApplication;

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

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

          // myExpress.js
          let http = require('http');
          function createApplication({
              let app = function (req,res{
                  res.end('hahha');
              }
              app.listen = function ({
                  let server = http.createServer(app)
                  server.listen(...arguments);

              }
              return app;
          }

          module.exports = createApplication;

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

          其實(shí)我們不傳入app,也就是說,讓app不是一個(gè)方法,也是可以的。

          我們可以改成這樣。

          // myExpress.js
          let http = require('http');
          function createApplication({
              let app = {};

              app.listen = function ({
                  let server = http.createServer(function (req, res{
                      res.end('hahha')
                  })
                  server.listen(...arguments);

              }
              return app;
          }

          module.exports = createApplication;

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

          .

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

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

          // myExpress.js
          let http = require('http');
          function createApplication({
              let app = {};
              app.routes = []
              app.get = function (path, handler{
                  let layer = {
                      method'get',
                      path,
                      handler
                  }
                  app.routes.push(layer)
              }
              app.listen = function ({
                  let server = http.createServer(function (req, res{
                      
                      res.end('hahha')
                  })
                  server.listen(...arguments);
              }
              return app;
          }

          module.exports = createApplication;


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

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

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

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

          // myExpress.js
          let http = require('http');
          const url  = require('url');
          function createApplication({
              let app = {};
              app.routes = []
              app.get = function (path, handler{
                  let layer = {
                      method'get',
                      path,
                      handler
                  }
                  app.routes.push(layer)
              }
              app.listen = function ({
                  let server = http.createServer(function (req, res{
                      // 取出layer 
                      // 1. 獲取請(qǐng)求的方法
                      let m = req.method.toLocaleLowerCase();
                      let { pathname } = url.parse(req.url, true);
                      
                      // 2.找到對(duì)應(yīng)的路由,執(zhí)行回調(diào)方法
                      for (let i = 0 ; i< app.routes.length; i++){
                          let {method,path,handler} = app.routes[i]
                          if (method === m && path === pathname ) {
                              handler(req,res);
                          }
                      }
                      res.end('hahha')
                  })
                  server.listen(...arguments);
              }
              return app;
          }

          module.exports = createApplication;

          運(yùn)行一下代碼。可見運(yùn)行成功:

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

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

          // myExpress.js
          let http = require('http');
          const url  = require('url');
          function createApplication({
              。。。
              app.get = function (path, handler{
                  let layer = {
                      method'get',
                      path,
                      handler
                  }
                  app.routes.push(layer)
              }
              app.post = function (path, handler{
                  let layer = {
                      method'post',
                      path,
                      handler
                  }
                  app.routes.push(layer)
              }
              。。。
              return app;
          }

          module.exports = createApplication;

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

          // myExpress.js

          function createApplication({
              ... 
              http.METHODS.forEach(method => {
                  method = method.toLocaleLowerCase()
                  app[method] = function (path, handler{
                      let layer = {
                          method,
                          path,
                          handler
                      }
                      app.routes.push(layer)
                  }
              });
              ...
          }

          module.exports = createApplication;

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

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

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

          測試跑了一下,確實(shí)成功。

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

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

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

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

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

          app.all = function (path, handler){
                  let layer = {
                      method"all",
                      path,
                      handler
                  }
                  app.routes.push(layer)
              }

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

          app.listen = function ({
              let server = http.createServer(function (req, res{
                  // 取出layer 
                  // 1. 獲取請(qǐng)求的方法
                  let m = req.method.toLocaleLowerCase();
                  let { pathname } = url.parse(req.url, true);

                  // 2.找到對(duì)應(yīng)的路由,執(zhí)行回調(diào)方法
                  for (let i = 0 ; i< app.routes.length; i++){
                      let {method,path,handler} = app.routes[i]
                      if ((method === m || method === 'all') && (path === pathname || path === "*")) {
                          handler(req,res);
                      }
                  }
                  console.log(app.routes);
                  res.end('hahha')
              })
              server.listen(...arguments);
          }

          可見成功。

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

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

          app.use = function (path, handler{
              let layer = {
                  method"middle",
                  path,
                  handler
              }
              app.routes.push(layer)
          }

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

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

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

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

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

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

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

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

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

          如下代碼所示:

          // myExpress.js
          let http = require('http');
          const url = require('url');
          function createApplication({
              let app = {};
              app.routes = [];
              let index = 0;

              app.use = function (path, handler{
                  let layer = {
                      method"middle",
                      path,
                      handler
                  }
                  app.routes.push(layer)
              }
              app.all = function (path, handler{
                  let layer = {
                      method"all",
                      path,
                      handler
                  }
                  app.routes.push(layer)
              }
              http.METHODS.forEach(method => {
                  method = method.toLocaleLowerCase()
                  app[method] = function (path, handler{
                      let layer = {
                          method,
                          path,
                          handler
                      }
                      app.routes.push(layer)
                  }
              });
              app.listen = function ({
                  let server = http.createServer(function (req, res{
                      // 取出layer 
                      // 1. 獲取請(qǐng)求的方法
                      let m = req.method.toLocaleLowerCase();
                      let { pathname } = url.parse(req.url, true);

                      // 2.找到對(duì)應(yīng)的路由,執(zhí)行回調(diào)方法
                      function next({
                          // 已經(jīng)迭代完整個(gè)數(shù)組,還是沒有找到匹配的路徑
                          if (index === app.routes.length) return res.end('Cannot find ')
                          let { method, path, handler } = app.routes[index++] // 每次調(diào)用next就去下一個(gè)layer
                          if (method === 'middle') { // 處理中間件
                              if (path === '/' || path === pathname || pathname.starWidth(path + '/')) {
                                  handler(req, res, next)
                              } else { // 繼續(xù)遍歷
                                  next();
                              }
                          } else { // 處理路由
                              if ((method === m || method === 'all') && (path === pathname || path === "*")) {
                                  handler(req, res);
                              } else {
                                  next();
                              }
                          }
                      }

                      next()
                      res.end('hahha')
                  })
                  server.listen(...arguments);
              }
              return app;
          }

          module.exports = createApplication;

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

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

          因?yàn)椋覀兪褂弥虚g件的時(shí)候,是可以不用傳遞路由的。例如:

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

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

          app.use = function (path, handler{
              if(typeof path !== "string") { // 第一個(gè)參數(shù)不是字符串,說明不是路徑,而是方法
                  handler = path;
                  path = "/"
              }
              let layer = {
                  method"middle",
                  path,
                  handler
              }
              app.routes.push(layer)
          }

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

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

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

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

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

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

          什么是錯(cuò)誤中間件?

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

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

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

          舉個(gè)例子:

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

          例如:

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

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

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


          function next(err{
              // 已經(jīng)迭代完整個(gè)數(shù)組,還是沒有找到匹配的路徑
              if (index === app.routes.length) return res.end('Cannot find ')
              let { method, path, handler } = app.routes[index++] // 每次調(diào)用next就去下一個(gè)layer
              if( err ){ // 如果有錯(cuò)誤,應(yīng)該尋找中間件執(zhí)行。
                  if(handler.length === 4) { //找到錯(cuò)誤中間件
                      handler(err,req,res,next)
                  }else { // 繼續(xù)徐州
                      next(err) 
                  }
              }else {
                  if (method === 'middle') { // 處理中間件
                      if (path === '/' || path === pathname || pathname.starWidth(path + '/')) {
                          handler(req, res, next)
                      } else { // 繼續(xù)遍歷
                          next();
                      }
                  } else { // 處理路由
                      if ((method === m || method === 'all') && (path === pathname || path === "*")) {
                          handler(req, res);
                      } else {
                          next();
                      }
                  }
              }
          }

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

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

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

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

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

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

          最后

          歡迎 關(guān)注公眾號(hào)《前端陽光》,有更多的手寫原理文章,也可以加入技術(shù)交流群和內(nèi)推群,公眾號(hào)收集了各廠的內(nèi)推碼,快來獲取吧!

          瀏覽 40
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美成人破处视频 | 啪啪啪AV网 | 日本亲与子乱人妻hd | 国产又爽 又黄 在线看 | 好操逼|