動(dòng)手實(shí)現(xiàn)一個(gè) Koa 框架(萬(wàn)字實(shí)戰(zhàn)好文)
用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)。下面這張圖可以直觀的看到Express和koa在功能上的區(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)看,Koa跟Express有幾個(gè)明顯的區(qū)別:
?ctx替代了req和res?可以使用JS的新API了,比如async和await
手寫源碼
手寫源碼前我們看看用到了哪些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.use:app是Koa的一個(gè)實(shí)例,app.use看起來(lái)是一個(gè)添加中間件的實(shí)例方法。?app.listen:?jiǎn)?dòng)服務(wù)器的實(shí)例方法?ctx:這個(gè)是Koa的上下文,看起來(lái)替代了以前的req和res?async和await:支持新的語(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.jsconst 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)可以看出Koa和Express的一個(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。Koa的compose也單獨(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)里面使用req和res創(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和nextreturn 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,直接就resolveif (!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傳入的req和res來(lái)構(gòu)建ctx這個(gè)上下文,官方源碼長(zhǎng)這樣:

這段代碼里面context,ctx,response,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.那request和req有啥區(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ū)別就是request是Koa包裝過(guò)的req,req是原生的請(qǐng)求對(duì)象。response和res也是類似的。3.既然request和response都只是包裝過(guò)的語(yǔ)法糖,那其實(shí)Koa沒有這兩個(gè)變量也能跑起來(lái)。所以我們拎骨架的時(shí)候完全可以將這兩個(gè)變量踢出去,這下骨架就清晰了。
那我們踢出response和request后再來(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.jsconst 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就囊括了response和request,所以這里的context也是一個(gè)語(yǔ)法糖。因?yàn)槲覀兦懊嬉呀?jīng)踢了response和request這兩個(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)在我們ctx和fn都構(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; // 取出bodyreturn 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.method和ctx.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.Koa是Express原班人馬寫的一個(gè)新框架。2.Koa使用了JS的新API,比如async和await。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
·END·
匯聚精彩的免費(fèi)實(shí)戰(zhàn)教程
喜歡本文,點(diǎn)個(gè)“在看”告訴我


