目前比較流行的nodejs框架有express、koa、egg.js,還有就是和ts相關(guān)的框架nest.js。
無(wú)論是哪種框架,其核心都是基于中間件來(lái)實(shí)現(xiàn)的,而中間件執(zhí)行的方式都跟洋蔥模型有關(guān),它們的差別主要也是在洋蔥模型的執(zhí)行方式上。
什么是洋蔥模型?
洋蔥模型,就像洋蔥一樣,一層包裹一層,而nodejs框架的執(zhí)行就像是中間穿過(guò)洋蔥的一條線(xiàn),而每一層洋蔥皮就代表一個(gè)中間件,進(jìn)入時(shí)穿過(guò)多少層,出來(lái)時(shí)還得穿出多少層,具有先進(jìn)后出(棧)的特點(diǎn)。

穿進(jìn)來(lái)時(shí):middleware1 -> middleware2 -> middleware3 -> center
穿出來(lái)時(shí):center -> middleware3 -> middleware2 -> middleware1
- 穿進(jìn)來(lái)時(shí),中間件的切換主要靠
next()關(guān)鍵字來(lái)實(shí)現(xiàn)的 - 穿出來(lái)時(shí),則是按中間件執(zhí)行完畢后,按照原路徑反回去
Express
Express在 Node.js 初期就是一個(gè)熱度較高、成熟的 Web 框架,并且包括的應(yīng)用場(chǎng)景非常齊全。同時(shí)基于 Express,也誕生了一些場(chǎng)景型的框架,常見(jiàn)的就如上面我們提到的 Nest.js框架
KOA
隨著nodejs發(fā)展,出現(xiàn)了以await/async為核心的語(yǔ)法糖,Express原班人馬為了實(shí)現(xiàn)一個(gè)高可用、高性能、更健壯,并且符合當(dāng)前Node.js 版本的框架,開(kāi)發(fā)出了可定制的 KOA框架。
Egg.js 就是在 KOA 基礎(chǔ)上,做了各種比較成熟的中間件和模塊,可以說(shuō)是在 KOA框架基礎(chǔ)上的最佳實(shí)踐,用以滿(mǎn)足開(kāi)發(fā)者開(kāi)箱即用的特性。
所以在對(duì)比差異時(shí),我們主要對(duì)比Express和KOA就可以看出它們間的主要區(qū)別
Express和KOA的差異
-
Express封裝、內(nèi)置了很多中間件,比如connect和router,而KOA則比較輕量,開(kāi)發(fā)者可以根據(jù)自身需求定制框架; -
Express是基于callback來(lái)處理中間件的,而KOA則是基于await/async; - 在異步執(zhí)行中間件時(shí),
Express并非嚴(yán)格按照洋蔥模型執(zhí)行中間件,而KOA則是嚴(yán)格遵循的。 -
Express使用callback捕獲異常,對(duì)于深層次的異常捕獲不了,Koa使用try catch,能更好地解決異常捕獲。
下面來(lái)以例子說(shuō)明一下兩個(gè)框架的執(zhí)行過(guò)程不一樣的地方:
Express示例
- 寫(xiě)個(gè)express服務(wù)器
- app.use:用于中間件和路由的處理。當(dāng)參數(shù)是函數(shù)時(shí),匹配所有路由;當(dāng)參數(shù)是字符串時(shí),匹配具體對(duì)應(yīng)的路由
mkdir node-demo
cd node-demo
npm init -y
mkdir src
touch express.js
const express = require('express')
const app = express()
const port = 3000
const server = app.listen(port, () => {
const host = server.address().address
const port = server.address().port
console.log(`Express server is listening on ${host}:${port}!`)
})
app.use((req, res, next) => {
console.log('middleware1 start')
next(); // 跳到下一層洋蔥中間件
console.log('middleware1 end')
})
app.use((req, res, next) => {
console.log('middleware2 start')
next(); // 跳到下一層洋蔥中間件
console.log('middleware2 end')
})
app.use((req, res, next) => {
console.log('middleware3 start')
next(); // 跳到下一層洋蔥中間件
console.log('middleware3 end')
})
app.get('/', (req, res) => {
res.send('hello express')
})
- 啟動(dòng)服務(wù)
node ./src/express-test.js - 在瀏覽器訪(fǎng)問(wèn)http://localhost:3000/,結(jié)果如下:
express服務(wù)器 - 關(guān)閉和管理服務(wù)
關(guān)閉服務(wù)的操作:在當(dāng)前目錄下ctrl+c結(jié)束進(jìn)程
KOA示例
KOA的用法其實(shí)和Express差不多,只不過(guò)Express內(nèi)置了router,而KOA需要自己定制:
- 安裝:
npm i koa koa-router chalk - 中間件的方法和Express基本一樣,只不過(guò)將
req, res參數(shù)換成了上下文ctx - 設(shè)置路由需要用路由實(shí)例方法
router.get方式,且要把路由實(shí)例設(shè)置到koa實(shí)例上app.use(router.routes())
const chalk = require('chalk')
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router()
const port = 9000
// 使用ctx上下文
app.use((ctx, next) => {
console.log('middleware1 start')
next(); // 跳到下一層洋蔥中間件
console.log('middleware1 end')
})
app.use((ctx, next) => {
console.log('middleware2 start')
next(); // 跳到下一層洋蔥中間件
console.log('middleware2 end')
})
app.use((ctx, next) => {
console.log('middleware3 start')
next(); // 跳到下一層洋蔥中間件
console.log('middleware3 end')
})
router.get('/', (ctx) => {
ctx.body = 'Hello koa!'
})
// 使用router
app.use(router.routes())
app.listen(port, 'localhost', () => {
console.log(chalk.yellow(`Express server is listening on ${port}!`))
})

可以看到如果是中間件中的代碼是同步的時(shí)候,兩者的的執(zhí)行順序是一樣的?,F(xiàn)在修改一下:
- 增加一個(gè)執(zhí)行異步操作的中間件
- 每一個(gè)中間件,都增加上
async await處理
// 這里只舉一例子,其它的中間件是一樣的
app.use(async (req, res, next) => {
console.log('middleware1 start')
await next(); // 跳到下一層洋蔥中間件
console.log('middleware1 end')
})
app.use(async (req, res, next) => {
console.log('async start');
await next();
await new Promise(
(resolve) =>
setTimeout(
() => {
console.log(`wait 1000 ms end`);
resolve()
},
1000
)
);
console.log('async end');
});
此時(shí),再觀察兩者的輸出順序:
Express是順序不是嚴(yán)格按照洋蔥模型的:

KOA的順序是嚴(yán)格按照洋蔥模型的:

發(fā)生這樣的原因是兩者基于的nodejs的版本不一樣導(dǎo)致的。
參考:
https://blog.csdn.net/xgangzai/article/details/109108387
http://www.itdecent.cn/p/6f7930687835/
