<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>

          動(dòng)手實(shí)現(xiàn)一個(gè) Koa 框架(萬(wàn)字實(shí)戰(zhàn)好文)

          共 12455字,需瀏覽 25分鐘

           ·

          2020-12-04 22:56

          Node.js寫一個(gè)web服務(wù)器,我前面已經(jīng)寫過(guò)兩篇文章了:

          ?第一篇是不使用任何框架也能搭建一個(gè)web服務(wù)器,主要是熟悉Node.js原生API的使用:使用Node.js原生API寫一個(gè)web服務(wù)器[1]?第二篇文章是看了Express的基本用法,更主要的是看了下他的源碼:手寫Express.js源碼[2]

          Express的源碼還是比較復(fù)雜的,自帶了路由處理和靜態(tài)資源支持等等功能,功能比較全面。與之相比,本文要講的Koa就簡(jiǎn)潔多了,Koa雖然是Express的原班人馬寫的,但是設(shè)計(jì)思路卻不一樣。Express更多是偏向All in one的思想,各種功能都集成在一起,而Koa本身的庫(kù)只有一個(gè)中間件內(nèi)核,其他像路由處理和靜態(tài)資源這些功能都沒有,全部需要引入第三方中間件庫(kù)才能實(shí)現(xiàn)。下面這張圖可以直觀的看到Expresskoa在功能上的區(qū)別,此圖來(lái)自于官方文檔[3]

          基于Koa的這種架構(gòu),我計(jì)劃會(huì)分幾篇文章來(lái)寫,全部都是源碼解析:

          ?Koa的核心架構(gòu)會(huì)寫一篇文章,也就是本文。?對(duì)于一個(gè)web服務(wù)器來(lái)說(shuō),路由是必不可少的,所以@koa/router會(huì)寫一篇文章。?另外可能會(huì)寫一些常用中間件,靜態(tài)文件支持或者bodyparser等等,具體還沒定,可能會(huì)有一篇或多篇文章。

          本文可運(yùn)行迷你版Koa代碼已經(jīng)上傳GitHub,拿下來(lái),一邊玩代碼一邊看文章效果更佳:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Node.js/KoaCore

          簡(jiǎn)單示例

          我寫源碼解析,一般都遵循一個(gè)簡(jiǎn)單的套路:先引入庫(kù),寫一個(gè)簡(jiǎn)單的例子,然后自己手寫源碼來(lái)替代這個(gè)庫(kù),并讓我們的例子順利運(yùn)行。本文也是遵循這個(gè)套路,由于Koa的核心庫(kù)只有中間件,所以我們寫出的例子也比較簡(jiǎn)單,也只有中間件。

          Hello World

          第一個(gè)例子是Hello World,隨便請(qǐng)求一個(gè)路徑都返回Hello World。

          const Koa = require("koa");const app = new Koa();
          app.use((ctx) => { ctx.body = "Hello World";});
          const port = 3001;app.listen(port, () => { console.log(`Server is running on http://127.0.0.1:${port}/`);});

          logger

          然后再來(lái)一個(gè)logger吧,就是記錄下處理當(dāng)前請(qǐng)求花了多長(zhǎng)時(shí)間:

          app.use(async (ctx, next) => {  const start = Date.now();  await next();  const ms = Date.now() - start;  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);});

          注意這個(gè)中間件應(yīng)該放到Hello World的前面。

          從上面兩個(gè)例子的代碼來(lái)看,KoaExpress有幾個(gè)明顯的區(qū)別:

          ?ctx替代了reqres?可以使用JS的新API了,比如asyncawait

          手寫源碼

          手寫源碼前我們看看用到了哪些API,這些就是我們手寫的目標(biāo):

          ?new Koa():首先肯定是Koa這個(gè)類了,因?yàn)樗褂?code style="box-sizing: border-box;padding: 3px 5px;color: rgb(255, 53, 2);line-height: 1.5;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;font-size: 14.4px;background: rgb(248, 245, 236);border-radius: 2px;">new進(jìn)行實(shí)例化,所以我們認(rèn)為他是一個(gè)類。?app.useappKoa的一個(gè)實(shí)例,app.use看起來(lái)是一個(gè)添加中間件的實(shí)例方法。?app.listen:?jiǎn)?dòng)服務(wù)器的實(shí)例方法?ctx:這個(gè)是Koa的上下文,看起來(lái)替代了以前的reqres?asyncawait:支持新的語(yǔ)法,而且能使用await next(),說(shuō)明next()返回的很可能是一個(gè)promise。

          本文的手寫源碼全部參照官方源碼寫成,文件名和函數(shù)名盡量保持一致,寫到具體的方法時(shí)我也會(huì)貼上官方源碼地址。Koa這個(gè)庫(kù)代碼并不多,主要都在這個(gè)文件夾里面:https://github.com/koajs/koa/tree/master/lib,下面我們開始吧。

          Koa類

          Koa項(xiàng)目的package.json里面的main這行代碼可以看出,整個(gè)應(yīng)用的入口是lib/application.js這個(gè)文件:

          "main": "lib/application.js",

          lib/application.js這個(gè)文件就是我們經(jīng)常用的Koa類,雖然我們經(jīng)常叫他Koa類,但是在源碼里面這個(gè)類叫做Application。我們先來(lái)寫一下這個(gè)類的殼吧:

          // application.js
          const Emitter = require("events");
          // module.exports 直接導(dǎo)出Application類module.exports = class Application extends Emitter { // 構(gòu)造函數(shù)先運(yùn)行下父類的構(gòu)造函數(shù) // 再進(jìn)行一些初始化工作 constructor() { super();
          // middleware實(shí)例屬性初始化為一個(gè)空數(shù)組,用來(lái)存儲(chǔ)后續(xù)可能的中間件 this.middleware = []; }};

          這段代碼我們可以看出,Koa直接使用class關(guān)鍵字來(lái)申明類了,看過(guò)我之前Express源碼解析的朋友可能還有印象,Express源碼里面還是使用的老的prototype來(lái)實(shí)現(xiàn)面向?qū)ο蟮摹K?code style="box-sizing: border-box;padding: 3px 5px;color: rgb(255, 53, 2);line-height: 1.5;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;font-size: 14.4px;background: rgb(248, 245, 236);border-radius: 2px;">Koa項(xiàng)目介紹里面的Expressive middleware for node.js using ES2017 async functions并不是一句虛言,它不僅支持ES2017新的API,而且在自己的源碼里面里面也是用的新API。我想這也是Koa要求運(yùn)行環(huán)境必須是node v7.6.0 or higher的原因吧。所以到這里我們其實(shí)已經(jīng)可以看出KoaExpress的一個(gè)重大區(qū)別了,那就是:Express使用老的API,兼容性更強(qiáng),可以在老的Node.js版本上運(yùn)行;Koa因?yàn)槭褂昧诵翧PI,只能在v7.6.0或者更高版本上運(yùn)行了。

          這段代碼還有個(gè)點(diǎn)需要注意,那就是Application繼承自Node.js原生的EventEmitter類,這個(gè)類其實(shí)就是一個(gè)發(fā)布訂閱模式,可以訂閱和發(fā)布消息,我在另一篇文章里面詳細(xì)講過(guò)他的源碼[4]。所以他有些方法如果在application.js里面找不到,那可能就是繼承自EventEmitter,比如下圖這行代碼:

          這里有this.on這個(gè)方法,看起來(lái)他應(yīng)該是Application的一個(gè)實(shí)例方法,但是這個(gè)文件里面沒有,其實(shí)他就是繼承自EventEmitter,是用來(lái)給error這個(gè)事件添加回調(diào)函數(shù)的。這行代碼if里面的this.listenerCount也是EventEmitter的一個(gè)實(shí)例方法。

          Application類完全是JS面向?qū)ο蟮倪\(yùn)用,如果你對(duì)JS面向?qū)ο筮€不是很熟悉,可以先看看這篇文章:https://juejin.im/post/6844904069887164423。

          app.use

          從我們前面的使用示例可以看出app.use的作用就是添加一個(gè)中間件,我們?cè)跇?gòu)造函數(shù)里面也初始化了一個(gè)變量middleware,用來(lái)存儲(chǔ)中間件,所以app.use的代碼就很簡(jiǎn)單了,將接收到的中間件塞到這個(gè)數(shù)組就行:

          use(fn) {  // 中間件必須是一個(gè)函數(shù),不然就報(bào)錯(cuò)  if (typeof fn !== "function")    throw new TypeError("middleware must be a function!");
          // 處理邏輯很簡(jiǎn)單,將接收到的中間件塞入到middleware數(shù)組就行 this.middleware.push(fn); return this;}

          注意app.use方法最后返回了this,這個(gè)有點(diǎn)意思,為什么要返回this呢?這個(gè)其實(shí)我之前在其他文章講過(guò)的[5]:類的實(shí)例方法返回this可以實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。比如這里的app.use就可以連續(xù)點(diǎn)點(diǎn)點(diǎn)了,像這樣:

          app.use(middlewaer1).use(middlewaer2).use(middlewaer3)

          為什么會(huì)有這種效果呢?因?yàn)檫@里的this其實(shí)就是當(dāng)前實(shí)例,也就是app,所以app.use()的返回值就是app,app上有個(gè)實(shí)例方法use,所以可以繼續(xù)點(diǎn)app.use().use()。

          app.use的官方源碼看這里: https://github.com/koajs/koa/blob/master/lib/application.js#L122

          app.listen

          在前面的示例中,app.listen的作用是用來(lái)啟動(dòng)服務(wù)器,看過(guò)前面用原生API實(shí)現(xiàn)web服務(wù)器的朋友都知道,要啟動(dòng)服務(wù)器需要調(diào)用原生的http.createServer,所以這個(gè)方法就是用來(lái)調(diào)用http.createServer的。

          listen(...args) {  const server = http.createServer(this.callback());  return server.listen(...args);}

          這個(gè)方法本身其實(shí)沒有太多可說(shuō)的,只是調(diào)用http模塊啟動(dòng)服務(wù)而已,主要的邏輯都在this.callback()里面了。

          app.listen的官方源碼看這里:https://github.com/koajs/koa/blob/master/lib/application.js#L79

          app.callback

          this.callback()是傳給http.createServer的回調(diào)函數(shù),也是一個(gè)實(shí)例函數(shù),這個(gè)函數(shù)必須符合http.createServer的參數(shù)形式,也就是

          http.createServer(function(req, res){})

          所以this.callback()的返回值必須是一個(gè)函數(shù),而且是這種形式function(req, res){}。

          除了形式必須符合外,this.callback()具體要干什么呢?他是http模塊的回調(diào)函數(shù),所以他必須處理所有的網(wǎng)絡(luò)請(qǐng)求,所有處理邏輯都必須在這個(gè)方法里面。但是Koa的處理邏輯是以中間件的形式存在的,對(duì)于一個(gè)請(qǐng)求來(lái)說(shuō),他必須一個(gè)一個(gè)的穿過(guò)所有的中間件,具體穿過(guò)的邏輯,你當(dāng)然可以遍歷middleware這個(gè)數(shù)組,將里面的方法一個(gè)一個(gè)拿出來(lái)處理,當(dāng)然也可以用業(yè)界更常用的方法:compose。

          compose一般來(lái)說(shuō)就是將一系列方法合并成一個(gè)方法來(lái)方便調(diào)用,具體實(shí)現(xiàn)的形式并不是固定的,有面試中常見的用reduce實(shí)現(xiàn)的compose[6],也有像Koa這樣根據(jù)自己需求單獨(dú)實(shí)現(xiàn)的compose。Koacompose也單獨(dú)封裝了一個(gè)庫(kù)koa-compose,這個(gè)庫(kù)源碼也是我們必須要看的,我們一步一步來(lái),先把this.callback寫出來(lái)吧。

          callback() {  // compose來(lái)自koa-compose庫(kù),就是將中間件合并成一個(gè)函數(shù)  // 我們需要自己實(shí)現(xiàn)  const fn = compose(this.middleware);
          // callback返回值必須符合http.createServer參數(shù)形式 // 即 (req, res) => {} const handleRequest = (req, res) => { const ctx = this.createContext(req, res); return this.handleRequest(ctx, fn); };
          return handleRequest;}

          這個(gè)方法先用koa-compose將中間件都合成了一個(gè)函數(shù)fn,然后在http.createServer的回調(diào)里面使用reqres創(chuàng)建了一個(gè)Koa常用的上下文ctx,然后再調(diào)用this.handleRequest來(lái)真正處理網(wǎng)絡(luò)請(qǐng)求。注意這里的this.handleRequest是個(gè)實(shí)例方法,和當(dāng)前方法里面的局部變量handleRequest并不是一個(gè)東西。這幾個(gè)方法我們一個(gè)一個(gè)來(lái)看下。

          this.callback對(duì)應(yīng)的官方源碼看這里:https://github.com/koajs/koa/blob/master/lib/application.js#L143

          koa-compose

          koa-compose雖然被作為了一個(gè)單獨(dú)的庫(kù),但是他的作用卻很關(guān)鍵,所以我們也來(lái)看看他的源碼吧。koa-compose的作用是將一個(gè)中間件組成的數(shù)組合并成一個(gè)方法以便外部調(diào)用。我們先來(lái)回顧下一個(gè)Koa中間件的結(jié)構(gòu):

          function middleware(ctx, next) {}

          這個(gè)數(shù)組就是有很多這樣的中間件:

          [  function middleware1(ctx, next) {},  function middleware2(ctx, next) {}]

          Koa的合并思路并不復(fù)雜,就是讓compose再返回一個(gè)函數(shù),返回的這個(gè)函數(shù)會(huì)開始這個(gè)數(shù)組的遍歷工作:

          function compose(middleware) {  // 參數(shù)檢查,middleware必須是一個(gè)數(shù)組  if (!Array.isArray(middleware))    throw new TypeError("Middleware stack must be an array!");  // 數(shù)組里面的每一項(xiàng)都必須是一個(gè)方法  for (const fn of middleware) {    if (typeof fn !== "function")      throw new TypeError("Middleware must be composed of functions!");  }
          // 返回一個(gè)方法,這個(gè)方法就是compose的結(jié)果 // 外部可以通過(guò)調(diào)用這個(gè)方法來(lái)開起中間件數(shù)組的遍歷 // 參數(shù)形式和普通中間件一樣,都是context和next return function (context, next) { return dispatch(0); // 開始中間件執(zhí)行,從數(shù)組第一個(gè)開始
          // 執(zhí)行中間件的方法 function dispatch(i) { let fn = middleware[i]; // 取出需要執(zhí)行的中間件
          // 如果i等于數(shù)組長(zhǎng)度,說(shuō)明數(shù)組已經(jīng)執(zhí)行完了 if (i === middleware.length) { fn = next; // 這里讓fn等于外部傳進(jìn)來(lái)的next,其實(shí)是進(jìn)行收尾工作,比如返回404 }
          // 如果外部沒有傳收尾的next,直接就resolve if (!fn) { return Promise.resolve(); }
          // 執(zhí)行中間件,注意傳給中間件接收的參數(shù)應(yīng)該是context和next // 傳給中間件的next是dispatch.bind(null, i + 1) // 所以中間件里面調(diào)用next的時(shí)候其實(shí)調(diào)用的是dispatch(i + 1),也就是執(zhí)行下一個(gè)中間件 try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err); } } };}

          上面代碼主要的邏輯就是這行:

          return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));

          這里的fn就是我們自己寫的中間件,比如文章開始那個(gè)logger,我們稍微改下看得更清楚:

          const logger = async (ctx, next) => {  const start = Date.now();  await next();  const ms = Date.now() - start;  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);};
          app.use(logger);

          那我們compose里面執(zhí)行的其實(shí)是:

          logger(context, dispatch.bind(null, i + 1));

          也就是說(shuō)logger接收到的next其實(shí)是dispatch.bind(null, i + 1),你調(diào)用next()的時(shí)候,其實(shí)調(diào)用的是dispatch(i + 1),這樣就達(dá)到了執(zhí)行數(shù)組下一個(gè)中間件的效果。

          另外由于中間件在返回前還包裹了一層Promise.resolve,所以我們所有自己寫的中間件,無(wú)論你是否用了Promise,next調(diào)用后返回的都是一個(gè)Promise,所以你可以使用await next()。

          koa-compose的源碼看這里:https://github.com/koajs/compose/blob/master/index.js

          app.createContext

          上面用到的this.createContext也是一個(gè)實(shí)例方法。這個(gè)方法根據(jù)http.createServer傳入的reqres來(lái)構(gòu)建ctx這個(gè)上下文,官方源碼長(zhǎng)這樣:

          這段代碼里面context,ctxresponse,res,request,req,app這幾個(gè)變量相互賦值,頭都看暈了。其實(shí)完全沒必要陷入這堆面條里面去,我們只需要將他的思路和骨架拎清楚就行,那怎么來(lái)拎呢?

          1.首先搞清楚他這么賦值的目的,他的目的其實(shí)很簡(jiǎn)單,就是為了使用方便。通過(guò)一個(gè)變量可以很方便的拿到其他變量,比如我現(xiàn)在只有request,但是我想要的是req,怎么辦呢?通過(guò)這種賦值后,直接用request.req就行。其他的類似,這種面條式的賦值我很難說(shuō)好還是不好,但是使用時(shí)確實(shí)很方便,缺點(diǎn)就是看源碼時(shí)容易陷進(jìn)去。2.requestreq有啥區(qū)別?這兩個(gè)變量長(zhǎng)得這么像,到底是干啥的?這就要說(shuō)到Koa對(duì)于原生req的擴(kuò)展,我們知道http.createServer的回調(diào)里面會(huì)傳入req作為請(qǐng)求對(duì)象的描述,里面可以拿到請(qǐng)求的header啊,method啊這些變量。但是Koa覺得這個(gè)req提供的API不好用,所以他在這個(gè)基礎(chǔ)上擴(kuò)展了一些API,其實(shí)就是一些語(yǔ)法糖,擴(kuò)展后的req就變成了request。之所以擴(kuò)展后還保留的原始的req,應(yīng)該也是想為用戶提供更多選擇吧。所以這兩個(gè)變量的區(qū)別就是requestKoa包裝過(guò)的req,req是原生的請(qǐng)求對(duì)象。responseres也是類似的。3.既然requestresponse都只是包裝過(guò)的語(yǔ)法糖,那其實(shí)Koa沒有這兩個(gè)變量也能跑起來(lái)。所以我們拎骨架的時(shí)候完全可以將這兩個(gè)變量踢出去,這下骨架就清晰了。

          那我們踢出responserequest后再來(lái)寫下createContext這個(gè)方法:

          // 創(chuàng)建上下文ctx對(duì)象的函數(shù)createContext(req, res) {  const context = Object.create(this.context);  context.app = this;  context.req = req;  context.res = res;
          return context;}

          這下整個(gè)世界感覺都清爽了,context上的東西也一目了然了。但是我們的context最初是來(lái)自this.context的,這個(gè)變量還必須看下。

          app.createContext對(duì)應(yīng)的官方源碼看這里:https://github.com/koajs/koa/blob/master/lib/application.js#L177

          context.js

          上面的this.context其實(shí)就是來(lái)自context.js,所以我們先在Application構(gòu)造函數(shù)里面添加這個(gè)變量:

          // application.js
          const context = require("./context");
          // 構(gòu)造函數(shù)里面constructor() { // 省略其他代碼 this.context = context;}

          然后再來(lái)看看context.js里面有啥,context.js的結(jié)構(gòu)大概是這個(gè)樣子:

          const delegate = require("delegates");
          module.exports = { inspect() {}, toJSON() {}, throw() {}, onerror() {},};
          const proto = module.exports;
          delegate(proto, "response") .method("set") .method("append") .access("message") .access("body");
          delegate(proto, "request") .method("acceptsLanguages") .method("accepts") .access("querystring") .access("socket");

          這段代碼里面context導(dǎo)出的是一個(gè)對(duì)象proto,這個(gè)對(duì)象本身有一些方法,inspect,toJSON之類的。然后還有一堆delegate().method()delegate().access()之類的。嗯,這個(gè)是干啥的呢?要知道這個(gè)的作用,我們需要去看delegates這個(gè)庫(kù):https://github.com/tj/node-delegates,這個(gè)庫(kù)也是tj大神寫的。一般使用是這樣的:

          delegate(proto, target).method("set");

          這行代碼的作用是,當(dāng)你調(diào)用proto.set()方法時(shí),其實(shí)是轉(zhuǎn)發(fā)給了proto[target],實(shí)際調(diào)用的是proto[target].set()。所以就是proto代理了對(duì)target的訪問(wèn)。

          那用在我們context.js里面是啥意思呢?比如這行代碼:

          delegate(proto, "response")  .method("set");

          這行代碼的作用是,當(dāng)你調(diào)用proto.set()時(shí),實(shí)際去調(diào)用proto.response.set(),將proto換成ctx就是:當(dāng)你調(diào)用ctx.set()時(shí),實(shí)際調(diào)用的是ctx.response.set()。這么做的目的其實(shí)也是為了使用方便,可以少寫一個(gè)response。而且ctx不僅僅代理response,還代理了request,所以你還可以通過(guò)ctx.accepts()這樣來(lái)調(diào)用到ctx.request.accepts()。一個(gè)ctx就囊括了responserequest,所以這里的context也是一個(gè)語(yǔ)法糖。因?yàn)槲覀兦懊嬉呀?jīng)踢了responserequest這兩個(gè)語(yǔ)法糖,context作為包裝了這兩個(gè)語(yǔ)法糖的語(yǔ)法糖,我們也一起踢掉吧。在Application的構(gòu)造函數(shù)里面直接將this.context賦值為空對(duì)象:

          // application.jsconstructor() {    // 省略其他代碼  this.context = {};}

          現(xiàn)在語(yǔ)法糖都踢掉了,整個(gè)Koa的結(jié)構(gòu)就更清晰了,ctx上面也只有幾個(gè)必須的變量:

          ctx = {  app,  req,  res}

          context.js對(duì)應(yīng)的源碼看這里:https://github.com/koajs/koa/blob/master/lib/context.js

          app.handleRequest

          現(xiàn)在我們ctxfn都構(gòu)造好了,那我們處理請(qǐng)求其實(shí)就是調(diào)用fn,ctx是作為參數(shù)傳給他的,所以app.handleRequest代碼就可以寫出來(lái)了:

          // 處理具體請(qǐng)求handleRequest(ctx, fnMiddleware) {  const handleResponse = () => respond(ctx);
          // 調(diào)用中間件處理 // 所有處理完后就調(diào)用handleResponse返回請(qǐng)求 return fnMiddleware(ctx) .then(handleResponse) .catch((err) => { console.log("Somethis is wrong: ", err); });}

          我們看到compose庫(kù)返回的fn雖然支持第二個(gè)參數(shù)用來(lái)收尾,但是Koa并沒有用他,如果不傳的話,所有中間件執(zhí)行完返回的就是一個(gè)空的promise,所以可以用then接著他后面處理。后面要進(jìn)行的處理就只有一個(gè)了,就是將處理結(jié)果返回給請(qǐng)求者的,這也就是respond需要做的。

          app.handleRequest對(duì)應(yīng)的源碼看這里:https://github.com/koajs/koa/blob/master/lib/application.js#L162

          respond

          respond是一個(gè)輔助方法,并不在Application類里面,他要做的就是將網(wǎng)絡(luò)請(qǐng)求返回:

          function respond(ctx) {  const res = ctx.res; // 取出res對(duì)象  const body = ctx.body; // 取出body
          return res.end(body); // 用res返回body}

          大功告成

          現(xiàn)在我們可以用自己寫的Koa替換官方的Koa來(lái)運(yùn)行我們開頭的例子了,不過(guò)logger這個(gè)中間件運(yùn)行的時(shí)候會(huì)有點(diǎn)問(wèn)題,因?yàn)樗旅孢@行代碼用到了語(yǔ)法糖:

          console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);

          這里的ctx.methodctx.url在我們構(gòu)建的ctx上并不存在,不過(guò)沒關(guān)系,他不就是個(gè)req的語(yǔ)法糖嘛,我們從ctx.req上拿就行,所以上面這行代碼改為:

          console.log(`${ctx.req.method} ${ctx.req.url} - ${ms}ms`);

          總結(jié)

          通過(guò)一層一層的抽絲剝繭,我們成功拎出了Koa的代碼骨架,自己寫了一個(gè)迷你版的Koa

          這個(gè)迷你版代碼已經(jīng)上傳GitHub,大家可以拿下來(lái)玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Node.js/KoaCore

          最后我們?cè)賮?lái)總結(jié)下本文的要點(diǎn)吧:

          1.KoaExpress原班人馬寫的一個(gè)新框架。2.Koa使用了JS的新API,比如asyncawait。3.Koa的架構(gòu)和Express有很大區(qū)別。4.Express的思路是大而全,內(nèi)置了很多功能,比如路由,靜態(tài)資源等,而且Express的中間件也是使用路由同樣的機(jī)制實(shí)現(xiàn)的,整個(gè)代碼更復(fù)雜。Express源碼可以看我之前這篇文章:手寫Express.js源碼[7]5.Koa的思路看起來(lái)更清晰,Koa本身的庫(kù)只是一個(gè)內(nèi)核,只有中間件功能,來(lái)的請(qǐng)求會(huì)依次經(jīng)過(guò)每一個(gè)中間件,然后再出來(lái)返回給請(qǐng)求者,這就是大家經(jīng)常聽說(shuō)的“洋蔥模型”。6.想要Koa支持其他功能,必須手動(dòng)添加中間件。作為一個(gè)web服務(wù)器,路由可以算是基本功能了,所以下一遍文章我們會(huì)來(lái)看看Koa官方的路由庫(kù)@koa/router,敬請(qǐng)關(guān)注。

          參考資料

          Koa官方文檔:https://github.com/koajs/koa

          Koa源碼地址:https://github.com/koajs/koa/tree/master/lib

          文章的最后,感謝你花費(fèi)寶貴的時(shí)間閱讀本文,如果本文給了你一點(diǎn)點(diǎn)幫助或者啟發(fā),請(qǐng)不要吝嗇你的贊和GitHub小星星,你的支持是作者持續(xù)創(chuàng)作的動(dòng)力。

          作者博文GitHub項(xiàng)目地址:https://github.com/dennis-jiang/Front-End-Knowledges

          作者掘金文章匯總:https://juejin.im/post/5e3ffc85518825494e2772fd

          我也搞了個(gè)公眾號(hào)[進(jìn)擊的大前端],不打廣告,不寫水文,只發(fā)高質(zhì)量原創(chuàng),歡迎關(guān)注~

          References

          [1]?使用Node.js原生API寫一個(gè)web服務(wù)器:?https://juejin.im/post/6887797543212843016
          [2]?手寫Express.js源碼:?https://juejin.im/post/6890358903960240142
          [3]?此圖來(lái)自于官方文檔:?https://github.com/koajs/koa/blob/master/docs/koa-vs-express.md
          [4]?我在另一篇文章里面詳細(xì)講過(guò)他的源碼:?https://juejin.im/post/6844904101331877895
          [5]?這個(gè)其實(shí)我之前在其他文章講過(guò)的:?https://juejin.im/post/6844904084571439118#heading-7
          [6]?面試中常見的用reduce實(shí)現(xiàn)的compose:?https://juejin.im/post/6844904061821517832
          [7]?手寫Express.js源碼:?https://juejin.im/post/6890358903960240142


          ●?【尤大出品】面向未來(lái)的前端構(gòu)建工具 - Vite

          ●?一杯茶的時(shí)間,上手 Koa2 + MySQL 開發(fā)

          ●?前端模塊化的前世今生(萬(wàn)字實(shí)戰(zhàn)好文)



          ·END·

          圖雀社區(qū)

          匯聚精彩的免費(fèi)實(shí)戰(zhàn)教程



          關(guān)注公眾號(hào)回復(fù) z 拉學(xué)習(xí)交流群


          喜歡本文,點(diǎn)個(gè)“在看”告訴我

          瀏覽 73
          點(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>
                  欧美成人一区二区三区片免费 | 午夜无码影院在线 | 波多野吉衣被操50分钟 | 男人天堂亚洲努力打造 | 精品人妻无码一区二区三级精东 |