這是
學習源碼總體架構系列
第七篇。總體架構這詞語好像有點大,姑且就算是源碼總體結構吧,主要就是學習是代碼總體結構,不深究其餘不是主線的具體函數的實現。本篇文章學習的是實際倉庫的代碼。
學習源碼總體架構系列
文章以下:html
1. 學習 jQuery 源碼總體架構 | 2. 學習 underscore 源碼總體架構 | 3. 學習 lodash 源碼總體架構 | 4. 學習 sentry 源碼總體架構
| 5. 學習 vuex 源碼總體架構 | 6. 學習 axios 源碼總體架構
感興趣的讀者能夠點擊閱讀。
其餘源碼計劃中的有:express
、vue-rotuer
、redux
、 react-redux
等源碼,不知什麼時候能寫完(哭泣),歡迎持續關注我(若川)。前端
源碼類文章,通常閱讀量不高。已經有能力看懂的,本身就看了。不想看,不敢看的就不會去看源碼。
因此個人文章,儘可能寫得讓想看源碼又不知道怎麼看的讀者能看懂。vue
若是你簡歷上一不當心寫了熟悉koa
,面試官大機率會問:node
一、koa
洋蔥模型怎麼實現的。
二、若是中間件中的next()
方法報錯了怎麼辦。
三、co
的原理是怎樣的。
等等問題
導讀
文章經過例子調試koa
,梳理koa
的主流程,來理解koa-compose
洋蔥模型原理和co
庫的原理,相信看完必定會有所收穫。react
本文學習的koa
版本是v2.11.0
。克隆的官方倉庫的master
分支。
截至目前(2020年3月11日),最新一次commit
是2020-01-04 07:41 Olle Jonsson
eda27608
,build: Drop unused Travis sudo: false directive (#1416)
。ios
本文倉庫在這裏若川的 koa-analysis github 倉庫 https://github.com/lxchuan12/koa-analysis。求個star
呀。git
先star
一下個人倉庫,再把它git clone https://github.com/lxchuan12/koa-analysis.git
克隆下來。不用管你是否用過nodejs
。會一點點promise、generator、async、await
等知識便可看懂。若是一點點也不會,能夠邊看阮一峯老師的《ES6標準入門》相關章節。跟着文章節奏調試和示例代碼調試,動手調試(用vscode
或者chrome
)印象更加深入。文章長段代碼不用細看,能夠調試時再細看。看這類源碼文章百遍,可能不如本身多調試幾遍。也歡迎加我微信交流lxchuan12
。es6
# 克隆個人這個倉庫 git clone https://github.com/lxchuan12/koa-analysis.git # chrome 調試: # 全局安裝 http-server npm i -g http-server hs koa/examples/ # 能夠指定端口 -p 3001 # hs -p 3001 koa/examples/ # 瀏覽器中打開 # 而後在瀏覽器中打開localhost:8080,開心的把代碼調試起來
這裏把這個examples
文件夾作個簡單介紹。
github
middleware
文件夾是用來vscode
調試總體流程的。simpleKoa
文件夾是koa
簡化版,爲了調試koa-compose
洋蔥模型如何串聯起來各個中間件的。koa-convert
文件夾是用來調試koa-convert
和co
源碼的。co-generator
文件夾是模擬實現co
的示例代碼。以前,我在知乎回答了一個問題一年內的前端看不懂前端框架源碼怎麼辦?
推薦了一些資料,閱讀量還不錯,你們有興趣能夠看看。主要有四點:
web
1.藉助調試
2.搜索查閱相關高贊文章
3.把不懂的地方記錄下來,查閱相關文檔
4.總結
看源碼,調試很重要,因此我詳細寫下 koa
源碼調試方法,幫助一些可能不知道如何調試的讀者。
# 我已經克隆到個人koa-analysis倉庫了 git clone https://github.com/koajs/koa.git
// package.json { "name": "koa", "version": "2.11.0", "description": "Koa web app framework", "main": "lib/application.js", }
克隆源碼後,看package.json
找到main
,就知道入口文件是lib/application.js
了。
大概看完項目結構後發現沒有examples
文件夾(通常項目都會有這個文件夾,告知用戶如何使用該項目),這時仔細看README.md
。
若是看英文README.md
有些吃力,會發如今Community
標題下有一個中文文檔 v2.x。同時也有一個examples
倉庫。
# 我已經克隆下來到個人倉庫了 git clone https://github.com/koajs/examples.git
這時再開心的把examples
克隆到本身電腦。能夠安裝好依賴,逐個研究學習下這裏的例子,而後可能就一不當心掌握了koa
的基本用法。固然,我這裏不詳細寫這一塊了,我是本身手寫一些例子來調試。
繼續看文檔會發現使用指南講述編寫中間件
。
koa-compose
例子來調試學習 koa-compose
前,先看兩張圖。
在koa
中,請求響應都放在中間件的第一個參數context
對象中了。
再引用Koa中文文檔中的一段:
若是您是前端開發人員,您能夠將 next()
; 以前的任意代碼視爲「捕獲」階段,這個簡易的 gif
說明了 async
函數如何使咱們可以恰當地利用堆棧流來實現請求和響應流:
- 建立一個跟蹤響應時間的日期
- 等待下一個中間件的控制
- 建立另外一個日期跟蹤持續時間
- 等待下一個中間件的控制
- 將響應主體設置爲「Hello World」
- 計算持續時間
- 輸出日誌行
- 計算響應時間
- 設置
X-Response-Time
頭字段- 交給 Koa 處理響應
讀者們看完這個gif圖,也能夠思考下如何實現的。根據表現,能夠猜想是next
是一個函數,並且返回的多是一個promise
,被await
調用。
看到這個gif
圖,我把以前寫的examples/koa-compose
的調試方法含淚刪除了。默默寫上gif
圖上的這些代碼,想着這個讀者們更容易讀懂。
我把這段代碼寫在這裏 koa/examples/middleware/app.js
便於調試。
在項目路徑下配置新建.vscode/launch.json文件,program
配置爲本身寫的koa/examples/middleware/app.js
文件。
<details>
<summary>.vscode/launch.json 代碼,點擊這裏展開/收縮,能夠複製</summary>
{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "啓動程序", "skipFiles": [ "<node_internals>/**" ], "program": "${workspaceFolder}/koa/examples/middleware/app.js" } ] }
</details>
按F5鍵
開始調試,調試時先走主流程,必要的地方打上斷點,不用一開始就關心細枝末節。
斷點調試要領:
賦值語句能夠一步跳過,看返回值便可,後續詳細再看。
函數執行須要斷點跟着看,也能夠結合註釋和上下文倒推這個函數作了什麼。
上述比較囉嗦的寫了一堆調試方法。主要是想着授人予魚不如授人予漁
,這樣換成其餘源碼也會調試了。
簡單說下chrome
調試nodejs
,chrome
瀏覽器打開chrome://inspect
,點擊配置configure...配置127.0.0.1:端口號
(端口號在Vscode
調試控制檯顯示了)。
更多能夠查看English Debugging Guide
中文調試指南
喜歡看視頻的讀者也能夠看慕課網這個視頻node.js調試入門,講得仍是比較詳細的。
不過我感受在chrome
調試nodejs
項目體驗不是很好(多是我方式不對),因此我大部分具體的代碼時都放在html
文件script
形式,在chrome
調試了。
new Koa()
結果app
是什麼看源碼我習慣性看它的實例對象結構,通常全部屬性和方法都放在實例對象上了,並且會經過原型鏈查找形式查找最頂端的屬性和方法。
用koa/examples/middleware/app.js
文件調試時,先看下執行new Koa()
以後,app
是什麼,有個初步印象。
// 文件 koa/examples/middleware/app.js const Koa = require('../../lib/application'); // const Koa = require('koa'); // 這裏打個斷點 const app = new Koa(); // x-response-time // 這裏打個斷點 app.use(async (ctx, next) => { });
在調試控制檯ctrl + 反引號鍵(通常在
Tab上方的按鍵)喚起
,輸入app
,按enter
鍵打印app
。會有一張這樣的圖。
VScode
也有一個代碼調試神器插件Debug Visualizer
。
安裝好後插件後,按ctrl + shift + p
,輸入Open a new Debug Visualizer View
,來使用,輸入app
,顯示是這樣的。
不過目前體驗來看,相對還比較雞肋,只能顯示一級,並且只能顯示對象,相信之後會更好。更多玩法能夠查看它的文檔。
我把koa實例對象比較完整的用xmind
畫出來了,大概看看就好,有個初步印象。
接着,咱們能夠看下app 實例、context、request、request
的官方文檔。
能夠真正使用的時候再去仔細看文檔。
經過F5啓動調試(直接跳到下一個斷點處)
、F10單步跳過
、F11單步調試
等,配合重要的地方斷點,調試完總體代碼,其實比較容易整理出以下主流程的代碼。
class Emitter{ // node 內置模塊 constructor(){ } } class Koa extends Emitter{ constructor(options){ super(); options = options || {}; this.middleware = []; this.context = { method: 'GET', url: '/url', body: undefined, set: function(key, val){ console.log('context.set', key, val); }, }; } use(fn){ this.middleware.push(fn); return this; } listen(){ const fnMiddleware = compose(this.middleware); const ctx = this.context; const handleResponse = () => respond(ctx); const onerror = function(){ console.log('onerror'); }; fnMiddleware(ctx).then(handleResponse).catch(onerror); } } function respond(ctx){ console.log('handleResponse'); console.log('response.end', ctx.body); }
重點就在listen
函數裏的compose
這個函數,接下來咱們就詳細來欣賞下這個函數。
經過app.use()
添加了若干函數,可是要把它們串起來執行呀。像上文的gif
圖同樣。
compose
函數,傳入一個數組,返回一個函數。對入參是否是數組和校驗數組每一項是否是函數。
function compose (middleware) { if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } // 傳入對象 context 返回Promise return function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] if (i === middleware.length) fn = next if (!fn) return Promise.resolve() try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err) } } } }
把簡化的代碼和koa-compose
代碼寫在了一個文件中。koa/examples/simpleKoa/koa-compose.js
hs koa/examples/ # 而後能夠打開localhost:8080/simpleKoa,開心的把代碼調試起來
不過這樣好像仍是有點麻煩,我還把這些代碼放在codepen
https://codepen.io/lxchuan12/pen/wvarPEb中,直接能夠在線調試啦。是否是以爲很貼心^_^,本身多調試幾遍便於消化理解。
你會發現compose
就是相似這樣的結構(移除一些判斷)。
// 這樣就可能更好理解了。 // simpleKoaCompose const [fn1, fn2, fn3] = this.middleware; const fnMiddleware = function(context){ return Promise.resolve( fn1(context, function next(){ return Promise.resolve( fn2(context, function next(){ return Promise.resolve( fn3(context, function next(){ return Promise.resolve(); }) ) }) ) }) ); }; fnMiddleware(ctx).then(handleResponse).catch(onerror);
也就是說koa-compose
返回的是一個Promise
,Promise
中取出第一個函數(app.use
添加的中間件),傳入context
和第一個next
函數來執行。
第一個next
函數裏也是返回的是一個Promise
,Promise
中取出第二個函數(app.use
添加的中間件),傳入context
和第二個next
函數來執行。
第二個next
函數裏也是返回的是一個Promise
,Promise
中取出第三個函數(app.use
添加的中間件),傳入context
和第三個next
函數來執行。
第三個...
以此類推。最後一箇中間件中有調用next
函數,則返回Promise.resolve
。若是沒有,則不執行next
函數。
這樣就把全部中間件串聯起來了。這也就是咱們常說的洋蔥模型。
不得不說很是驚豔,「玩仍是大神會玩」。
這種把函數存儲下來的方式,在不少源碼中都有看到。好比lodash
源碼的惰性求值,vuex
也是把action
等函數存儲下,最後纔去調用。
搞懂了koa-compose
洋蔥模型實現的代碼,其餘代碼就不在話下了。
仔細看文檔,文檔中寫了三種捕獲錯誤的方式。
ctx.onerror
中間件中的錯誤捕獲app.on('error', (err) => {})
最外層實例事件監聽形式app.onerror = (err) => {}
重寫onerror
自定義形式// application.js 文件 class Application extends Emitter { // 代碼有簡化組合 listen(){ const fnMiddleware = compose(this.middleware); if (!this.listenerCount('error')) this.on('error', this.onerror); const onerror = err => ctx.onerror(err); fnMiddleware(ctx).then(handleResponse).catch(onerror); } onerror(err) { // 代碼省略 // ... } }
ctx.onerror
lib/context.js
文件中,有一個函數onerror
,並且有這麼一行代碼this.app.emit('error', err, this)
。
module.exports = { onerror(){ // delegate // app 是在new Koa() 實例 this.app.emit('error', err, this); } }
app.use(async (ctx, next) => { try { await next(); } catch (err) { err.status = err.statusCode || err.status || 500; throw err; } });
try catch
錯誤或被fnMiddleware(ctx).then(handleResponse).catch(onerror);
,這裏的onerror
是ctx.onerror
而ctx.onerror
函數中又調用了this.app.emit('error', err, this)
,因此在最外圍app.on('error',err => {})
能夠捕獲中間件鏈中的錯誤。
由於koa
繼承自events模塊
,因此有'emit'和on
等方法)
koa1
中主要是generator
函數。koa2
中會自動轉換generator
函數。
// Koa 將轉換 app.use(function *(next) { const start = Date.now(); yield next; const ms = Date.now() - start; console.log(`${this.method} ${this.url} - ${ms}ms`); });
在vscode/launch.json
文件,找到這個program
字段,修改成"program": "${workspaceFolder}/koa/examples/koa-convert/app.js"
。
經過F5啓動調試(直接跳到下一個斷點處)
、F10單步跳過
、F11單步調試
調試走一遍流程。重要地方斷點調試。
app.use
時有一層判斷,是不是generator
函數,若是是則用koa-convert
暴露的方法convert
來轉換從新賦值,再存入middleware
,後續再使用。
class Koa extends Emitter{ use(fn) { if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'); if (isGeneratorFunction(fn)) { deprecate('Support for generators will be removed in v3. ' + 'See the documentation for examples of how to convert old middleware ' + 'https://github.com/koajs/koa/blob/master/docs/migration.md'); fn = convert(fn); } debug('use %s', fn._name || fn.name || '-'); this.middleware.push(fn); return this; } }
koa-convert
源碼挺多,核心代碼實際上是這樣的。
function convert(){ return function (ctx, next) { return co.call(ctx, mw.call(ctx, createGenerator(next))) } function * createGenerator (next) { return yield next() } }
最後仍是經過co
來轉換的。因此接下來看co
的源碼。
本小節的示例代碼都在這個文件夾koa/examples/co-generator
中,hs koa/example
,能夠自行打開https://localhost:8080/co-generator
調試查看。
看co
源碼前,先看幾段簡單代碼。
// 寫一個請求簡版請求 function request(ms= 1000) { return new Promise((resolve) => { setTimeout(() => { resolve({name: '若川'}); }, ms); }); }
// 獲取generator的值 function* generatorFunc(){ const res = yield request(); console.log(res, 'generatorFunc-res'); } generatorFunc(); // 報告,我不會輸出你想要的結果的
簡單來講co
,就是把generator
自動執行,再返回一個promise
。generator
函數這玩意它不自動執行呀,還要一步步調用next()
,也就是叫它走一步才走一步。
因此有了async、await
函數。
// await 函數 自動執行 async function asyncFunc(){ const res = await request(); console.log(res, 'asyncFunc-res await 函數 自動執行'); } asyncFunc(); // 輸出結果
也就是說co
須要作的事情,是讓generator
向async、await
函數同樣自動執行。
這時,咱們來模擬實現初版的co
。根據generator
的特性,其實容易寫出以下代碼。
// 獲取generator的值 function* generatorFunc(){ const res = yield request(); console.log(res, 'generatorFunc-res'); } function coSimple(gen){ gen = gen(); console.log(gen, 'gen'); const ret = gen.next(); const promise = ret.value; promise.then(res => { gen.next(res); }); } coSimple(generatorFunc); // 輸出了想要的結果 // {name: "若川"}"generatorFunc-res"
可是實際上,不會上面那麼簡單的。有多是多個yield
和傳參數的狀況。
傳參能夠經過這以下兩行代碼來解決。
const args = Array.prototype.slice.call(arguments, 1); gen = gen.apply(ctx, args);
兩個yield
,我大不了從新調用一下promise.then
,搞定。
// 多個yeild,傳參狀況 function* generatorFunc(suffix = ''){ const res = yield request(); console.log(res, 'generatorFunc-res' + suffix); const res2 = yield request(); console.log(res2, 'generatorFunc-res-2' + suffix); } function coSimple(gen){ const ctx = this; const args = Array.prototype.slice.call(arguments, 1); gen = gen.apply(ctx, args); console.log(gen, 'gen'); const ret = gen.next(); const promise = ret.value; promise.then(res => { const ret = gen.next(res); const promise = ret.value; promise.then(res => { gen.next(res); }); }); } coSimple(generatorFunc, ' 哎呀,我真的是後綴');
問題是確定不止兩次,無限次的yield
的呢,這時確定要把重複的封裝起來。並且返回是promise
,這就實現了以下版本的代碼。
function* generatorFunc(suffix = ''){ const res = yield request(); console.log(res, 'generatorFunc-res' + suffix); const res2 = yield request(); console.log(res2, 'generatorFunc-res-2' + suffix); const res3 = yield request(); console.log(res3, 'generatorFunc-res-3' + suffix); const res4 = yield request(); console.log(res4, 'generatorFunc-res-4' + suffix); } function coSimple(gen){ const ctx = this; const args = Array.prototype.slice.call(arguments, 1); gen = gen.apply(ctx, args); console.log(gen, 'gen'); return new Promise((resolve, reject) => { onFulfilled(); function onFulfilled(res){ const ret = gen.next(res); next(ret); } function next(ret) { const promise = ret.value; promise && promise.then(onFulfilled); } }); } coSimple(generatorFunc, ' 哎呀,我真的是後綴');
但第三版的模擬實現簡版co
中,尚未考慮報錯和一些參數合法的狀況。
co
源碼這時來看看co
的源碼,報錯和錯誤的狀況,錯誤時調用reject
,是否是就好理解了一些呢。
function co(gen) { var ctx = this; var args = slice.call(arguments, 1) // we wrap everything in a promise to avoid promise chaining, // which leads to memory leak errors. // see https://github.com/tj/co/issues/180 return new Promise(function(resolve, reject) { // 把參數傳遞給gen函數並執行 if (typeof gen === 'function') gen = gen.apply(ctx, args); // 若是不是函數 直接返回 if (!gen || typeof gen.next !== 'function') return resolve(gen); onFulfilled(); /** * @param {Mixed} res * @return {Promise} * @api private */ function onFulfilled(res) { var ret; try { ret = gen.next(res); } catch (e) { return reject(e); } next(ret); } /** * @param {Error} err * @return {Promise} * @api private */ function onRejected(err) { var ret; try { ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); } /** * Get the next value in the generator, * return a promise. * * @param {Object} ret * @return {Promise} * @api private */ // 反覆執行調用本身 function next(ret) { // 檢查當前是否爲 Generator 函數的最後一步,若是是就返回 if (ret.done) return resolve(ret.value); // 確保返回值是promise對象。 var value = toPromise.call(ctx, ret.value); // 使用 then 方法,爲返回值加上回調函數,而後經過 onFulfilled 函數再次調用 next 函數。 if (value && isPromise(value)) return value.then(onFulfilled, onRejected); // 在參數不符合要求的狀況下(參數非 Thunk 函數和 Promise 對象),將 Promise 對象的狀態改成 rejected,從而終止執行。 return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' + 'but the following object was passed: "' + String(ret.value) + '"')); } }); }
文檔裏寫的挺全面的。簡單來講koa2
語法更先進,更容易深度定製(egg.js
、think.js
、底層框架都是koa
)。
文章經過授人予魚不如授人予魚
的方式,告知如何調試源碼,看完了koa-compose
洋蔥模型實現,koa-convert
和co
等源碼。
koa-compose
是將app.use
添加到middleware
數組中的中間件(函數),經過使用Promise
串聯起來,next()
返回的是一個promise
。
koa-convert
判斷app.use
傳入的函數是不是generator
函數,若是是則用koa-convert
來轉換,最終仍是調用的co
來轉換。
co
源碼實現原理:其實就是經過不斷的調用generator
函數的next()
函數,來達到自動執行generator
函數的效果(相似async、await函數的自動自行
)。
koa
框架總結:主要就是四個核心概念,洋蔥模型(把中間件串聯起來),http
請求上下文(context
)、http
請求對象、http
響應對象。
本文倉庫在這裏若川的 koa-analysis github 倉庫 https://github.com/lxchuan12/koa-analysis。求個star
呀。
git clone https://github.com/lxchuan12/koa-analysis.git
再強烈建議下按照本文閱讀最佳方式,克隆代碼下來,動手調試代碼學習更加深入。
若是讀者發現有不妥或可改善之處,再或者哪裏沒寫明白的地方,歡迎評論指出,也歡迎加我微信交流
lxchuan12
。另外以爲寫得不錯,對您有些許幫助,能夠點贊、評論、轉發分享,也是對筆者的一種支持,萬分感謝。
僅供參考
一、koa
洋蔥模型怎麼實現的。
能夠參考上文整理的簡版koa-compose
做答。
// 這樣就可能更好理解了。 // simpleKoaCompose const [fn1, fn2, fn3] = this.middleware; const fnMiddleware = function(context){ return Promise.resolve( fn1(context, function next(){ return Promise.resolve( fn2(context, function next(){ return Promise.resolve( fn3(context, function next(){ return Promise.resolve(); }) ) }) ) }) ); }; fnMiddleware(ctx).then(handleResponse).catch(onerror);
答:app.use() 把中間件函數存儲在middleware
數組中,最終會調用koa-compose
導出的函數compose
返回一個promise
,中間函數的第一個參數ctx
是包含響應和請求的一個對象,會不斷傳遞給下一個中間件。next
是一個函數,返回的是一個promise
。
二、若是中間件中的next()
方法報錯了怎麼辦。
可參考上文整理的錯誤處理做答。
ctx.onerror = function { this.app.emit('error', err, this); }; listen(){ const fnMiddleware = compose(this.middleware); if (!this.listenerCount('error')) this.on('error', this.onerror); const onerror = err => ctx.onerror(err); fnMiddleware(ctx).then(handleResponse).catch(onerror); } onerror(err) { // 代碼省略 // ... }
答:中間件鏈錯誤會由ctx.onerror
捕獲,該函數中會調用this.app.emit('error', err, this)
(由於koa
繼承自events模塊
,因此有'emit'和on
等方法),可使用app.on('error', (err) => {})
,或者app.onerror = (err) => {}
進行捕獲。
三、co
的原理是怎樣的。
答:co
的原理是經過不斷調用generator
函數的next
方法來達到自動執行generator
函數的,相似async、await
函數自動執行。
答完,面試官可能以爲小夥子仍是蠻懂koa
的啊。固然也可能繼續追問,直到答不出...
學完了總體流程,koa-compose
、koa-convert
和co
的源碼。
還能仔細看看看http
請求上下文(context
)、http
請求對象、http
響應對象的具體實現。
還能根據我文章說的調試方式調試koa 組織中的各類中間件,好比koa-bodyparser
, koa-router
,koa-jwt
,koa-session
、koa-cors
等等。
還能把examples
倉庫克隆下來,個人這個倉庫已經克隆了,挨個調試學習下源碼。
web
框架有不少,好比Express.js
,Koa.js
、Egg.js
、Nest.js
、Next.js
、Fastify.js
、Hapi.js
、Restify.js
、Loopback.io
、Sails.js
、Midway.js
等等。
還能把這些框架的優點劣勢、設計思想等學習下。
還能繼續學習HTTP
協議、TCP/IP
協議網絡相關,雖然不屬於koa
的知識,但需深刻學習掌握。
學無止境~~~
koa 官網 | koa 倉庫 | koa 組織 | koa2 中文文檔 | co 倉庫
知乎@姚大帥:多是目前市面上比較有誠意的Koa2源碼解讀
知乎@零小白:十分鐘帶你看完 KOA 源碼
微信開放社區@小丹の:多是目前最全的koa源碼解析指南
IVWEB官方帳號: KOA2框架原理解析和實現
深刻淺出vue.js 做者 berwin: 深刻淺出 Koa2 原理
阮一峯老師:co 函數庫的含義和用法
面試官問:JS的繼承
面試官問:JS的this指向
面試官問:可否模擬實現JS的call和apply方法
面試官問:可否模擬實現JS的bind方法
面試官問:可否模擬實現JS的new操做符
做者:常以若川爲名混跡於江湖。前端路上 | PPT愛好者 | 所知甚少,惟善學。
若川的博客,使用vuepress
重構了,閱讀體驗可能更好些
掘金專欄,歡迎關注~segmentfault
前端視野專欄,歡迎關注~
知乎前端視野專欄,歡迎關注~
語雀前端視野專欄,新增語雀專欄,歡迎關注~
github blog,相關源碼和資源都放在這裏,求個star
^_^~
可能比較有趣的微信公衆號,長按掃碼關注。歡迎加我微信lxchuan12
(註明來源,基原本者不拒),拉您進【前端視野交流羣】,長期交流學習~