国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > JS > 正文

如何從零開始手寫Koa2框架

2024-05-06 16:49:20
字體:
來源:轉載
供稿:網友

01、介紹

  • Koa-- 基于 Node.js 平臺的下一代 web 開發框架
  • Koa 是一個新的 web 框架,由 Express 幕后的原班人馬打造, 致力于成為 web 應用和 API 開發領域中的一個更小、更富有表現力、更健壯的基石。
  • 與其對應的 Express 來比,Koa 更加小巧、精壯,本文將帶大家從零開始實現 Koa 的源碼,從根源上解決大家對 Koa 的困惑
本文 Koa 版本為 2.7.0, 版本不一樣源碼可能會有變動

02、源碼目錄介紹

Koa 源碼目錄截圖

Koa2框架,Koa2

通過源碼目錄可以知道,Koa主要分為4個部分,分別是:

  • application: Koa 最主要的模塊, 對應 app 應用對象
  • context: 對應 ctx 對象
  • request: 對應 Koa 中請求對象
  • response: 對應 Koa 中響應對象

這4個文件就是 Koa 的全部內容了,其中 application 又是其中最核心的文件。我們將會從此文件入手,一步步實現 Koa 框架

03、實現一個基本服務器代碼目錄

my-application

const {createServer} = require('http');module.exports = class Application { constructor() { // 初始化中間件數組, 所有中間件函數都會添加到當前數組中 this.middleware = []; } // 使用中間件方法 use(fn) { // 將所有中間件函數添加到中間件數組中 this.middleware.push(fn); } // 監聽端口號方法 listen(...args) { // 使用nodejs的http模塊監聽端口號 const server = createServer((req, res) => {  /*  處理請求的回調函數,在這里執行了所有中間件函數  req 是 node 原生的 request 對象  res 是 node 原生的 response 對象  */  this.middleware.forEach((fn) => fn(req, res)); }) server.listen(...args); }}

index.js

// 引入自定義模塊const MyKoa = require('./js/my-application');// 創建實例對象const app = new MyKoa();// 使用中間件app.use((req, res) => { console.log('中間件函數執行了~~~111');})app.use((req, res) => { console.log('中間件函數執行了~~~222'); res.end('hello myKoa');})// 監聽端口號app.listen(3000, err => { if (!err) console.log('服務器啟動成功了'); else console.log(err);})

運行入口文件 index.js 后,通過瀏覽器輸入網址訪問 http://localhost:3000/ , 就可以看到結果了~~

神奇吧!一個最簡單的服務器模型就搭建完了。當然我們這個極簡服務器還存在很多問題,接下來讓我們一一解決

04、實現中間件函數的 next 方法

提取createServer的回調函數,封裝成一個callback方法(可復用)

// 監聽端口號方法listen(...args) { // 使用nodejs的http模塊監聽端口號 const server = createServer(this.callback()); server.listen(...args);}callback() { const handleRequest = (req, res) => { this.middleware.forEach((fn) => fn(req, res)); } return handleRequest;}

封裝compose函數實現next方法

// 負責執行中間件函數的函數function compose(middleware) { // compose方法返回值是一個函數,這個函數返回值是一個promise對象 // 當前函數就是調度 return (req, res) => { // 默認調用一次,為了執行第一個中間件函數 return dispatch(0); function dispatch(i) {  // 提取中間件數組的函數fn  let fn = middleware[i];  // 如果最后一個中間件也調用了next方法,直接返回一個成功狀態的promise對象  if (!fn) return Promise.resolve();  /*  dispatch.bind(null, i + 1)) 作為中間件函數調用的第三個參數,其實就是對應的next   舉個栗子:如果 i = 0 那么 dispatch.bind(null, 1))    --> 也就是如果調用了next方法 實際上就是執行 dispatch(1)     --> 它利用遞歸重新進來取出下一個中間件函數接著執行  fn(req, res, dispatch.bind(null, i + 1))   --> 這也是為什么中間件函數能有三個參數,在調用時我們傳進來了  */  return Promise.resolve(fn(req, res, dispatch.bind(null, i + 1))); } }}

使用compose函數

callback () { // 執行compose方法返回一個函數 const fn = compose(this.middleware);  const handleRequest = (req, res) => { // 調用該函數,返回值為promise對象 // then方法觸發了, 說明所有中間件函數都被調用完成 fn(req, res).then(() => {  // 在這里就是所有處理的函數的最后階段,可以允許返回響應了~ }); }  return handleRequest;}

修改入口文件 index.js 代碼

// 引入自定義模塊const MyKoa = require('./js/my-application');// 創建實例對象const app = new MyKoa();// 使用中間件app.use((req, res, next) => { console.log('中間件函數執行了~~~111'); // 調用next方法,就是調用堆棧中下一個中間件函數 next();})app.use((req, res, next) => { console.log('中間件函數執行了~~~222'); res.end('hello myKoa'); // 最后的next方法沒發調用下一個中間件函數,直接返回Promise.resolve() next();})// 監聽端口號app.listen(3000, err => { if (!err) console.log('服務器啟動成功了'); else console.log(err);})

此時我們實現了next方法,最核心的就是compose函數,極簡的代碼實現了功能,不可思議!

05、處理返回響應

定義返回響應函數respond

function respond(req, res) { // 獲取設置的body數據 let body = res.body;  if (typeof body === 'object') { // 如果是對象,轉化成json數據返回 body = JSON.stringify(body); res.end(body); } else { // 默認其他數據直接返回 res.end(body); }}

callback中調用

callback() { const fn = compose(this.middleware);  const handleRequest = (req, res) => { // 當中間件函數全部執行完畢時,會觸發then方法,從而執行respond方法返回響應 const handleResponse = () => respond(req, res); fn(req, res).then(handleResponse); }  return handleRequest;}

修改入口文件 index.js 代碼

// 引入自定義模塊const MyKoa = require('./js/my-application');// 創建實例對象const app = new MyKoa();// 使用中間件app.use((req, res, next) => { console.log('中間件函數執行了~~~111'); next();})app.use((req, res, next) => { console.log('中間件函數執行了~~~222'); // 設置響應內容,由框架負責返回響應~ res.body = 'hello myKoa';})// 監聽端口號app.listen(3000, err => { if (!err) console.log('服務器啟動成功了'); else console.log(err);})

此時我們就能根據不同響應內容做出處理了~當然還是比較簡單的,可以接著去擴展~

06、定義 Request 模塊

// 此模塊需要npm下載const parse = require('parseurl');const qs = require('querystring');module.exports = { /** * 獲取請求頭信息 */ get headers() { return this.req.headers; }, /** * 設置請求頭信息 */ set headers(val) { this.req.headers = val; }, /** * 獲取查詢字符串 */ get query() { // 解析查詢字符串參數 --> key1=value1&key2=value2 const querystring = parse(this.req).query; // 將其解析為對象返回 --> {key1: value1, key2: value2} return qs.parse(querystring); }}

07、定義 Response 模塊

module.exports = { /** * 設置響應頭的信息 */ set(key, value) { this.res.setHeader(key, value); }, /** * 獲取響應狀態碼 */ get status() { return this.res.statusCode; }, /** * 設置響應狀態碼 */ set status(code) { this.res.statusCode = code; }, /** * 獲取響應體信息 */ get body() { return this._body; }, /** * 設置響應體信息 */ set body(val) { // 設置響應體內容 this._body = val; // 設置響應狀態碼 this.status = 200; // json if (typeof val === 'object') {  this.set('Content-Type', 'application/json'); } },}

08、定義 Context 模塊

// 此模塊需要npm下載const delegate = require('delegates');const proto = module.exports = {};// 將response對象上的屬性/方法克隆到proto上delegate(proto, 'response') .method('set') // 克隆普通方法 .access('status') // 克隆帶有get和set描述符的方法 .access('body') // 將request對象上的屬性/方法克隆到proto上delegate(proto, 'request') .access('query') .getter('headers') // 克隆帶有get描述符的方法

09、揭秘 delegates 模塊

module.exports = Delegator;/** * 初始化一個 delegator. */function Delegator(proto, target) { // this必須指向Delegator的實例對象 if (!(this instanceof Delegator)) return new Delegator(proto, target); // 需要克隆的對象 this.proto = proto; // 被克隆的目標對象 this.target = target; // 所有普通方法的數組 this.methods = []; // 所有帶有get描述符的方法數組 this.getters = []; // 所有帶有set描述符的方法數組 this.setters = [];}/** * 克隆普通方法 */Delegator.prototype.method = function(name){ // 需要克隆的對象 var proto = this.proto; // 被克隆的目標對象 var target = this.target; // 方法添加到method數組中 this.methods.push(name); // 給proto添加克隆的屬性 proto[name] = function(){ /*  this指向proto, 也就是ctx  舉個栗子:ctx.response.set.apply(ctx.response, arguments)  arguments對應實參列表,剛好與apply方法傳參一致  執行ctx.set('key', 'value') 實際上相當于執行 response.set('key', 'value') */ return this[target][name].apply(this[target], arguments); }; // 方便鏈式調用 return this;};/** * 克隆帶有get和set描述符的方法. */Delegator.prototype.access = function(name){ return this.getter(name).setter(name);};/** * 克隆帶有get描述符的方法. */Delegator.prototype.getter = function(name){ var proto = this.proto; var target = this.target; this.getters.push(name); // 方法可以為一個已經存在的對象設置get描述符屬性 proto.__defineGetter__(name, function(){ return this[target][name]; }); return this;};/** * 克隆帶有set描述符的方法. */Delegator.prototype.setter = function(name){ var proto = this.proto; var target = this.target; this.setters.push(name); // 方法可以為一個已經存在的對象設置set描述符屬性 proto.__defineSetter__(name, function(val){ return this[target][name] = val; }); return this;};

10、使用 ctx 取代 req 和 res

修改 my-application

const {createServer} = require('http');const context = require('./my-context');const request = require('./my-request');const response = require('./my-response');module.exports = class Application { constructor() { this.middleware = []; // Object.create(target) 以target對象為原型, 創建新對象, 新對象原型有target對象的屬性和方法 this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); }  use(fn) { this.middleware.push(fn); }  listen(...args) { // 使用nodejs的http模塊監聽端口號 const server = createServer(this.callback()); server.listen(...args); }  callback() { const fn = compose(this.middleware);  const handleRequest = (req, res) => {  // 創建context  const ctx = this.createContext(req, res);  const handleResponse = () => respond(ctx);  fn(ctx).then(handleResponse); }  return handleRequest; }  // 創建context 上下文對象的方法 createContext(req, res) { /*  凡是req/res,就是node原生對象  凡是request/response,就是自定義對象  這是實現互相掛載引用,從而在任意對象上都能獲取其他對象的方法  */ const context = Object.create(this.context); const request = context.request = Object.create(this.request); const response = context.response = Object.create(this.response); context.app = request.app = response.app = this; context.req = request.req = response.req = req; context.res = request.res = response.res = res; request.ctx = response.ctx = context; request.response = response; response.request = request;  return context; }}// 將原來使用req,res的地方改用ctxfunction compose(middleware) { return (ctx) => { return dispatch(0); function dispatch(i) {  let fn = middleware[i];  if (!fn) return Promise.resolve();  return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1))); } }}function respond(ctx) { let body = ctx.body; const res = ctx.res; if (typeof body === 'object') { body = JSON.stringify(body); res.end(body); } else { res.end(body); }}

修改入口文件 index.js 代碼

// 引入自定義模塊const MyKoa = require('./js/my-application');// 創建實例對象const app = new MyKoa();// 使用中間件app.use((ctx, next) => { console.log('中間件函數執行了~~~111'); next();})app.use((ctx, next) => { console.log('中間件函數執行了~~~222'); // 獲取請求頭參數 console.log(ctx.headers); // 獲取查詢字符串參數 console.log(ctx.query); // 設置響應頭信息 ctx.set('content-type', 'text/html;charset=utf-8'); // 設置響應內容,由框架負責返回響應~ ctx.body = '<h1>hello myKoa</h1>';})// 監聽端口號app.listen(3000, err => { if (!err) console.log('服務器啟動成功了'); else console.log(err);})
到這里已經寫完了 Koa 主要代碼,有一句古話 - 看萬遍代碼不如寫上一遍。 還等什么,趕緊寫上一遍吧~
當你能夠寫出來,再去閱讀源碼,你會發現源碼如此簡單~

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 永福县| 宜良县| 新竹县| 孟连| 昆明市| 昭平县| 辽源市| 区。| 蓬莱市| 抚松县| 迭部县| 平塘县| 江永县| 密云县| 共和县| 蓝山县| 安西县| 衡阳县| 安庆市| 盐池县| 襄汾县| 天门市| 峨眉山市| 胶州市| 浪卡子县| 化德县| 龙井市| 洛扎县| 城固县| 都江堰市| 怀化市| 阿尔山市| 平定县| 兴义市| 敦化市| 綦江县| 墨脱县| 墨脱县| 墨脱县| 惠水县| 阿巴嘎旗|