詳解KOA2如何手寫(xiě)中間件(裝飾器模式)
前言
Koa 2.x 版本是當(dāng)下最流行的 NodeJS 框架, Koa 2.0 的源碼特別精簡(jiǎn),不像 Express 封裝的功能那么多,所以大部分的功能都是由 Koa 開(kāi)發(fā)團(tuán)隊(duì)(同 Express 是一家出品)和社區(qū)貢獻(xiàn)者針對(duì) Koa 對(duì) NodeJS 的封裝特性實(shí)現(xiàn)的中間件來(lái)提供的,用法非常簡(jiǎn)單,就是引入中間件,并調(diào)用 Koa 的 use 方法使用在對(duì)應(yīng)的位置,這樣就可以通過(guò)在內(nèi)部操作 ctx 實(shí)現(xiàn)一些功能,我們接下來(lái)就討論常用中間件的實(shí)現(xiàn)原理以及我們應(yīng)該如何開(kāi)發(fā)一個(gè) Koa 中間件供自己和別人使用。
Koa 的洋蔥模型介紹
我們本次不對(duì)洋蔥模型的實(shí)現(xiàn)原理進(jìn)行過(guò)多的刨析,主要根據(jù) API 的使用方式及洋蔥模型分析中間件是如何工作的。
洋蔥模型特點(diǎn)
// 引入 Koa const Koa = require("koa"); // 創(chuàng)建服務(wù) const app = new Koa(); app.use(async (ctx, next) => { console.log(1); await next(); console.log(2); }); app.use(async (ctx, next) => { console.log(3); await next(); console.log(4); }); app.use(async (ctx, next) => { console.log(5); await next(); console.log(6); }); // 監(jiān)聽(tīng)服務(wù) app.listen(3000); // 1 // 3 // 5 // 6 // 4 // 2
我們知道 Koa 的 use 方法是支持異步的,所以為了保證正常的按照洋蔥模型的執(zhí)行順序執(zhí)行代碼,需要在調(diào)用 next 的時(shí)候讓代碼等待,等待異步結(jié)束后再繼續(xù)向下執(zhí)行,所以我們?cè)?Koa 中都是建議使用 async/await 的,引入的中間件都是在 use 方法中調(diào)用,由此我們可以分析出每一個(gè) Koa 的中間件都是返回一個(gè) async 函數(shù)的。
koa-bodyparser 中間件模擬
想要分析 koa-bodyparser 的原理首先需要知道用法和作用, koa-bodyparser 中間件是將我們的 post 請(qǐng)求和表單提交的查詢字符串轉(zhuǎn)換成對(duì)象,并掛在 ctx.request.body 上,方便我們?cè)谄渌虚g件或接口處取值,使用前需提前安裝。
npm install koa koa-bodyparser
koa-bodyparser 具體用法如下:
koa-bodyparser 的用法
const Koa = require("koa"); const bodyParser = require("koa-bodyparser"); const app = new Koa(); // 使用中間件 app.use(bodyParser()); app.use(async (ctx, next) => { if (ctx.path === "/" && ctx.method === "POST") { // 使用中間件后 ctx.request.body 屬性自動(dòng)加上了 post 請(qǐng)求的數(shù)據(jù) console.log(ctx.request.body); } }); app.listen(3000);
根據(jù)用法我們可以看出 koa-bodyparser 中間件引入的其實(shí)是一個(gè)函數(shù),我們把它放在了 use 中執(zhí)行,根據(jù) Koa 的特點(diǎn),我們推斷出 koa-bodyparser 的函數(shù)執(zhí)行后應(yīng)該給我們返回了一個(gè) async 函數(shù),下面是我們模擬實(shí)現(xiàn)的代碼。
文件:my-koa-bodyparser.js
const querystring = require("querystring"); module.exports = function bodyParser() { return async (ctx, next) => { await new Promise((resolve, reject) => { // 存儲(chǔ)數(shù)據(jù)的數(shù)組 let dataArr = []; // 接收數(shù)據(jù) ctx.req.on("data", data => dataArr.push(data)); // 整合數(shù)據(jù)并使用 Promise 成功 ctx.req.on("end", () => { // 獲取請(qǐng)求數(shù)據(jù)的類型 json 或表單 let contentType = ctx.get("Content-Type"); // 獲取數(shù)據(jù) Buffer 格式 let data = Buffer.concat(dataArr).toString(); if (contentType === "application/x-www-form-urlencoded") { // 如果是表單提交,則將查詢字符串轉(zhuǎn)換成對(duì)象賦值給 ctx.request.body ctx.request.body = querystring.parse(data); } else if (contentType === "applaction/json") { // 如果是 json,則將字符串格式的對(duì)象轉(zhuǎn)換成對(duì)象賦值給 ctx.request.body ctx.request.body = JSON.parse(data); } // 執(zhí)行成功的回調(diào) resolve(); }); }); // 繼續(xù)向下執(zhí)行 await next(); }; };
在上面代碼中由幾點(diǎn)是需要我們注意的,即 next 的調(diào)用以及為什么通過(guò)流接收數(shù)據(jù)、處理數(shù)據(jù)和將數(shù)據(jù)掛在 ctx.request.body 要在 Promise 中進(jìn)行。
首先是 next 的調(diào)用,我們知道 Koa 的 next 執(zhí)行,其實(shí)就是在執(zhí)行下一個(gè)中間件的函數(shù),即下一個(gè) use 中的 async 函數(shù),為了保證后面的異步代碼執(zhí)行完畢后再繼續(xù)執(zhí)行當(dāng)前的代碼,所以我們需要使用 await 進(jìn)行等待,其次就是數(shù)據(jù)從接收到掛在 ctx.request.body 都在 Promise 中執(zhí)行,是因?yàn)樵诮邮諗?shù)據(jù)的操作是異步的,整個(gè)處理數(shù)據(jù)的過(guò)程需要等待異步完成后,再把數(shù)據(jù)掛在 ctx.request.body 上,可以保證我們?cè)谙乱粋€(gè) use 的 async 函數(shù)中可以在 ctx.request.body 上拿到數(shù)據(jù),所以我們使用 await 等待一個(gè) Promise 成功后再執(zhí)行 next 。
koa-better-body 中間件模擬
koa-bodyparser 在處理表單提交時(shí)還是顯得有一點(diǎn)弱,因?yàn)椴恢С治募蟼?,?koa-better-body 則彌補(bǔ)了這個(gè)不足,但是 koa-better-body 為 Koa 1.x 版本的中間件, Koa 1.x 的中間件都是使用 Generator 函數(shù)實(shí)現(xiàn)的,我們需要使用 koa-convert 將 koa-better-body 轉(zhuǎn)化成 Koa 2.x 的中間件。
npm install koa koa-better-body koa-convert path uuid
koa-better-body 具體用法如下:
koa-better-body 的用法
const Koa = require("koa"); const betterBody = require("koa-better-body"); const convert = require("koa-convert"); // 將 koa 1.0 中間轉(zhuǎn)化成 koa 2.0 中間件 const path = require("path"); const fs = require("fs"); const uuid = require("uuid/v1"); // 生成隨機(jī)串 const app = new Koa(); // 將 koa-better-body 中間件從 koa 1.0 轉(zhuǎn)化成 koa 2.0,并使用中間件 app.use(convert(betterBody({ uploadDir: path.resolve(__dirname, "upload") }))); app.use(async (ctx, next) => { if (ctx.path === "/" && ctx.method === "POST") { // 使用中間件后 ctx.request.fields 屬性自動(dòng)加上了 post 請(qǐng)求的文件數(shù)據(jù) console.log(ctx.request.fields); // 將文件重命名 let imgPath = ctx.request.fields.avatar[0].path; let newPath = path.resolve(__dirname, uuid()); fs.rename(imgPath, newPath); } }); app.listen(3000);
上面代碼中 koa-better-body 的主要功能就是將表單上傳的文件存入本地指定的文件夾下,并將文件流對(duì)象掛在了 ctx.request.fields 屬性上,我們接下來(lái)就模擬 koa-better-body 的功能實(shí)現(xiàn)一版基于 Koa 2.x 處理文件上傳的中間件。
文件:my-koa-better-body.js
const fs = require("fs"); const uuid = require("uuid/v1"); const path = require("path"); // 給 Buffer 擴(kuò)展 split 方法預(yù)備后面使用 Buffer.prototype.split = function (sep) { let len = Buffer.from(sep).length; // 分隔符所占的字節(jié)數(shù) let result = []; // 返回的數(shù)組 let start = 0; // 查找 Buffer 的起始位置 let offset = 0; // 偏移量 // 循環(huán)查找分隔符 while ((offset = this.indexOf(sep, start)) !== -1) { // 將分隔符之前的部分截取出來(lái)存入 result.push(this.slice(start, offset)); start = offset + len; } // 處理剩下的部分 result.push(this.slice(start)); // 返回結(jié)果 return result; } module.exports = function (options) { return async (ctx, next) => { await new Promise((resolve, reject) => { let dataArr = []; // 存儲(chǔ)讀取的數(shù)據(jù) // 讀取數(shù)據(jù) ctx.req.on("data", data => dataArr.push(data)); ctx.req.on("end", () => { // 取到請(qǐng)求體每段的分割線字符串 let bondery = `--${ctx.get("content-Type").split("=")[1]}`; // 獲取不同系統(tǒng)的換行符 let lineBreak = process.platform === "win32" ? "\r\n" : "\n"; // 非文件類型數(shù)據(jù)的最終返回結(jié)果 let fields = {}; // 分隔的 buffer 去掉沒(méi)用的頭和尾即開(kāi)頭的 '' 和末尾的 '--' dataArr = dataArr.split(bondery).slice(1, -1); // 循環(huán)處理 dataArr 中每一段 Buffer 的內(nèi)容 dataArr.forEach(lines => { // 對(duì)于普通值,信息由包含鍵名的行 + 兩個(gè)換行 + 數(shù)據(jù)值 + 換行組成 // 對(duì)于文件,信息由包含 filename 的行 + 兩個(gè)換行 + 文件內(nèi)容 + 換行組成 let [head, tail] = lines.split(`${lineBreak}${lineBreak}`); // 判斷是否是文件,如果是文件則創(chuàng)建文件并寫(xiě)入,如果是普通值則存入 fields 對(duì)象中 if (head.includes("filename")) { // 防止文件內(nèi)容含有換行而被分割,應(yīng)重新截取內(nèi)容并去掉最后的換行 let tail = lines.slice(head.length + 2 * lineBreak.length, -lineBreak.length); // 創(chuàng)建可寫(xiě)流并指定寫(xiě)入的路徑:絕對(duì)路徑 + 指定文件夾 + 隨機(jī)文件名,最后寫(xiě)入文件 fs.createWriteStream(path.join(__dirname, options.uploadDir, uuid())).end(tail); } else { // 是普通值取出鍵名 let key = head.match(/name="(\w+)"/)[1]; // 將 key 設(shè)置給 fields tail 去掉末尾換行后的內(nèi)容 fields[key] = tail.toString("utf8").slice(0, -lineBreak.length); } }); // 將處理好的 fields 對(duì)象掛在 ctx.request.fields 上,并完成 Promise ctx.request.fields = fields; resolve(); }); }); // 向下執(zhí)行 await next(); } }
上面的內(nèi)容邏輯可以通過(guò)代碼注釋來(lái)理解,就是模擬 koa-better-body 的功能邏輯,我們主要的關(guān)心點(diǎn)在于中間件實(shí)現(xiàn)的方式,上面功能實(shí)現(xiàn)的異步操作依然是讀取數(shù)據(jù),為了等待數(shù)據(jù)處理結(jié)束仍然在 Promise 中執(zhí)行,并使用 await 等待,Promise 執(zhí)行成功調(diào)用 next 。
koa-views 中間件模擬
Node 模板是我們經(jīng)常使用的工具用來(lái)在服務(wù)端幫我們渲染頁(yè)面,模板的種類繁多,因此出現(xiàn)了 koa-view 中間件,幫我們來(lái)兼容這些模板,先安裝依賴的模塊。
npm install koa koa-views ejs
下面是一個(gè) ejs 的模板文件:
文件:index.ejs
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>ejs</title> </head> <body> <%=name%> <%=age%> <%if (name=="panda") {%> panda <%} else {%> shen <%}%> <%arr.forEach(item => {%> <li><%=item%></li> <%})%> </body> </html>
koa-views 具體用法如下:
koa-views 的用法
const Koa = require("koa"); const views = require("koa-views"); const path = require("path"); const app = new Koa(); // 使用中間件 app.use(views(path.resolve(__dirname, "views"), { extension: "ejs" })); app.use(async (ctx, next) => { await ctx.render("index", { name: "panda", age: 20, arr: [1, 2, 3] }); }); app.listen(3000);
可以看出我們使用了 koa-views 中間件后,讓 ctx 上多了 render 方法幫助我們實(shí)現(xiàn)對(duì)模板的渲染和響應(yīng)頁(yè)面,就和直接使用 ejs 自帶的 render 方法一樣,并且從用法可以看出 render 方法是異步執(zhí)行的,所以需要使用 await 進(jìn)行等待,接下來(lái)我們就來(lái)模擬實(shí)現(xiàn)一版簡(jiǎn)單的 koa-views 中間件。
文件:my-koa-views.js
const fs = require("fs"); const path = require("path"); const { promisify } = require("util"); // 將讀取文件方法轉(zhuǎn)換成 Promise const readFile = promisify(fs.radFile); // 到處中間件 module.exports = function (dir, options) { return async (ctx, next) => { // 動(dòng)態(tài)引入模板依賴模塊 const view = require(options.extension); ctx.render = async (filename, data) => { // 異步讀取文件內(nèi)容 let tmpl = await readFile(path.join(dir, `${filename}.${options.extension}`), "utf8"); // 將模板渲染并返回頁(yè)面字符串 let pageStr = view.render(tmpl, data); // 設(shè)置響應(yīng)類型并響應(yīng)頁(yè)面 ctx.set("Content-Type", "text/html;charset=utf8"); ctx.body = pageStr; } // 繼續(xù)向下執(zhí)行 await next(); } }
掛在 ctx 上的 render 方法之所以是異步執(zhí)行的是因?yàn)閮?nèi)部讀取模板文件是異步執(zhí)行的,需要等待,所以 render 方法為 async 函數(shù),在中間件內(nèi)部動(dòng)態(tài)引入了我們使的用模板,如 ejs ,并在 ctx.render 內(nèi)部使用對(duì)應(yīng)的 render 方法獲取替換數(shù)據(jù)后的頁(yè)面字符串,并以 html 的類型響應(yīng)。
koa-static 中間件模擬
下面是 koa-static 中間件的用法,代碼使用的依賴如下,使用前需安裝。
npm install koa koa-static mime
koa-static 具體用法如下:
koa-static 的用法
const Koa = require("koa"); const static = require("koa-static"); const path = require("path"); const app = new Koa(); app.use(static(path.resolve(__dirname, "public"))); app.use(async (ctx, next) => { ctx.body = "hello world"; }); app.listen(3000);
通過(guò)使用和分析,我們知道了 koa-static 中間件的作用是在服務(wù)器接到請(qǐng)求時(shí),幫我們處理靜態(tài)文件,如果我們直接訪問(wèn)文件名的時(shí)候,會(huì)查找這個(gè)文件并直接響應(yīng),如果沒(méi)有這個(gè)文件路徑會(huì)當(dāng)作文件夾,并查找文件夾下的 index.html ,如果存在則直接響應(yīng),如果不存在則交給其他中間件處理。
文件:my-koa-static.js
const fs = require("fs"); const path = require("path"); const mime = require("mime"); const { promisify } = require("util"); // 將 stat 和 access 轉(zhuǎn)換成 Promise const stat = promisify(fs.stat); const access = promisify(fs.access) module.exports = function (dir) { return async (ctx, next) => { // 將訪問(wèn)的路由處理成絕對(duì)路徑,這里要使用 join 因?yàn)橛锌赡苁?/ let realPath = path.join(dir, ctx.path); try { // 獲取 stat 對(duì)象 let statObj = await stat(realPath); // 如果是文件,則設(shè)置文件類型并直接響應(yīng)內(nèi)容,否則當(dāng)作文件夾尋找 index.html if (statObj.isFile()) { ctx.set("Content-Type", `${mime.getType()};charset=utf8`); ctx.body = fs.createReadStream(realPath); } else { let filename = path.join(realPath, "index.html"); // 如果不存在該文件則執(zhí)行 catch 中的 next 交給其他中間件處理 await access(filename); // 存在設(shè)置文件類型并響應(yīng)內(nèi)容 ctx.set("Content-Type", "text/html;charset=utf8"); ctx.body = fs.createReadStream(filename); } } catch (e) { await next(); } } }
上面的邏輯中需要檢測(cè)路徑是否存在,由于我們導(dǎo)出的函數(shù)都是 async 函數(shù),所以我們將 stat 和 access 轉(zhuǎn)化成了 Promise,并用 try...catch 進(jìn)行捕獲,在路徑不合法時(shí)調(diào)用 next 交給其他中間件處理。
koa-router 中間件模擬
在 Express 框架中,路由是被內(nèi)置在了框架內(nèi)部,而 Koa 中沒(méi)有內(nèi)置,是使用 koa-router 中間件來(lái)實(shí)現(xiàn)的,使用前需要安裝。
npm install koa koa-router
koa-router 功能非常強(qiáng)大,下面我們只是簡(jiǎn)單的使用,并且根據(jù)使用的功能進(jìn)行模擬。
koa-router 的簡(jiǎn)單用法
const Koa = require("Koa"); const Router = require("koa-router"); const app = new Koa(); const router = new Router(); router.get("/panda", (ctx, next) => { ctx.body = "panda"; }); router.get("/panda", (ctx, next) => { ctx.body = "pandashen"; }); router.get("/shen", (ctx, next) => { ctx.body = "shen"; }) // 調(diào)用路由中間件 app.use(router.routes()); app.listen(3000);
從上面看出 koa-router 導(dǎo)出的是一個(gè)類,使用時(shí)需要?jiǎng)?chuàng)建一個(gè)實(shí)例,并且調(diào)用實(shí)例的 routes 方法將該方法返回的 async 函數(shù)進(jìn)行連接,但是在匹配路由的時(shí)候,會(huì)根據(jù)路由 get 方法中的路徑進(jìn)行匹配,并串行執(zhí)行內(nèi)部的回調(diào)函數(shù),當(dāng)所有回調(diào)函數(shù)執(zhí)行完畢之后會(huì)執(zhí)行整個(gè) Koa 串行的 next ,原理同其他中間件,我下面來(lái)針對(duì)上面使用的功能簡(jiǎn)易實(shí)現(xiàn)。
文件:my-koa-router.js
// 控制每一個(gè)路由層的類 class Layer { constructor(path, cb) { this.path = path; this.cb = cb; } match(path) { // 地址的路由和當(dāng)前配置路由相等返回 true,否則返回 false return path === this.path; } } // 路由的類 class Router { constructor() { // 存放每個(gè)路由對(duì)象的數(shù)組,{ path: /xxx, fn: cb } this.layers = []; } get(path, cb) { // 將路由對(duì)象存入數(shù)組中 this.layers.push(new Layer(path, cb)); } compose(ctx, next, handlers) { // 將匹配的路由函數(shù)串聯(lián)執(zhí)行 function dispatch(index) { // 如果當(dāng)前 index 個(gè)數(shù)大于了存儲(chǔ)路由對(duì)象的長(zhǎng)度,則執(zhí)行 Koa 的 next 方法 if(index >= handlers.length) return next(); // 否則調(diào)用取出的路由對(duì)象的回調(diào)執(zhí)行,并傳入一個(gè)函數(shù),在傳入的函數(shù)中遞歸 dispatch(index + 1) // 目的是為了執(zhí)行下一個(gè)路由對(duì)象上的回調(diào)函數(shù) handlers[index].cb(ctx, () => dispatch(index + 1)); } // 第一次執(zhí)行路由對(duì)象的回調(diào)函數(shù) dispatch(0); } routes() { return async (ctx, next) { // 當(dāng)前 next 是 Koa 自己的 next,即 Koa 其他的中間件 // 篩選出路徑相同的路由 let handlers = this.layers.filter(layer => layer.match(ctx.path)); this.compose(ctx, next, handlers); } } }
在上面我們創(chuàng)建了一個(gè) Router 類,定義了 get 方法,當(dāng)然還有 post 等,我們只實(shí)現(xiàn) get 意思一下, get 內(nèi)為邏輯為將調(diào)用 get 方法的參數(shù)函數(shù)和路由字符串共同構(gòu)建成對(duì)象存入了數(shù)組 layers ,所以我們創(chuàng)建了專門(mén)構(gòu)造路由對(duì)象的類 Layer ,方便擴(kuò)展,在路由匹配時(shí)我們可以根據(jù) ctx.path 拿到路由字符串,并通過(guò)該路由過(guò)濾調(diào)數(shù)組中與路由不匹配的路由對(duì)象,調(diào)用 compose 方法將過(guò)濾后的數(shù)組作為參數(shù) handlers 傳入,串行執(zhí)行路由對(duì)象上的回調(diào)函數(shù)。
compose 這個(gè)方法的實(shí)現(xiàn)思想非常的重要,在 Koa 源碼中用于串聯(lián)中間件,在 React 源碼中用于串聯(lián) redux 的 promise 、 thunk 和 logger 等模塊,我們的實(shí)現(xiàn)是一個(gè)簡(jiǎn)版,并沒(méi)有兼容異步,主要思想是遞歸 dispatch 函數(shù),每次取出數(shù)組中下一個(gè)路由對(duì)象的回調(diào)函數(shù)執(zhí)行,直到所有匹配的路由的回調(diào)函數(shù)都執(zhí)行完,執(zhí)行 Koa 的下一個(gè)中間件 next ,注意此處的 next 不同于數(shù)組中回調(diào)函數(shù)的參數(shù) next ,數(shù)組中路由對(duì)象回調(diào)函數(shù)的 next 代表下一個(gè)匹配路由的回調(diào)。
總結(jié)
上面我們分析和模擬了一些中間件,其實(shí)我們會(huì)理解 Koa 和 Express 相比較的優(yōu)勢(shì)是沒(méi)有那么繁重,開(kāi)發(fā)使用方便,需要的功能都可以用對(duì)應(yīng)的中間件來(lái)實(shí)現(xiàn),使用中間件可以給我們帶來(lái)一些好處,比如能將我們處理好的數(shù)據(jù)和新方法掛載在 ctx 上,方便后面 use 傳入的回調(diào)函數(shù)中使用,也可以幫我們處理一些公共邏輯,不至于在每一個(gè) use 的回調(diào)中都去處理,大大減少了冗余代碼,由此看來(lái)其實(shí)給 Koa 使用中間件的過(guò)程就是一個(gè)典型的 “裝飾器” 模式,在通過(guò)上面的分析之后相信大家也了解了 Koa 的 “洋蔥模型” 和異步特點(diǎn),知道該如何開(kāi)發(fā)自己的中間件了。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
對(duì)mac下nodejs 更新到最新版本的最新方法(推薦)
今天小編就為大家分享一篇對(duì)mac下nodejs 更新到最新版本的最新方法(推薦),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05用Electron寫(xiě)個(gè)帶界面的nodejs爬蟲(chóng)的實(shí)現(xiàn)方法
這篇文章主要介紹了用Electron寫(xiě)個(gè)帶界面的nodejs爬蟲(chóng)的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01koa+jwt實(shí)現(xiàn)token驗(yàn)證與刷新功能
這篇文章主要介紹了koa+jwt實(shí)現(xiàn)token驗(yàn)證與刷新功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05nodeJS?express路由學(xué)習(xí)req.body與req.query方法實(shí)例詳解
這篇文章主要為大家介紹了nodeJS?express路由學(xué)習(xí)req.body與req.query方法實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09前端如何更好的展示后端返回的十萬(wàn)條數(shù)據(jù)
這篇文章主要為大家介紹了前端如何更好的展示后端返回的十萬(wàn)條數(shù)據(jù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2021-11-11NodeJs內(nèi)存占用過(guò)高的排查實(shí)戰(zhàn)記錄
這篇文章主要給大家介紹了關(guān)于NodeJs內(nèi)存占用過(guò)高的排查實(shí)戰(zhàn)記錄,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05