上一遍博文中,咱們談到了tapable的用法,如今咱們來深刻一下tap到底是怎麼運行的, 怎麼處理,控制 tap 進去的鉤子函數,攔截器又是怎麼運行的.前端
先從同步函數開始分析,異步也就是回調而已;webpack
這裏有一個例子web
let SyncHook = require('./lib/SyncHook.js') let h1 = new SyncHook(['options']); h1.tap('A', function (arg) { console.log('A',arg); return 'b'; // 除非你在攔截器上的 register 上調用這個函數,否則這個返回值你拿不到. }) h1.tap('B', function () { console.log('b') }) h1.tap('C', function () { console.log('c') }) h1.tap('D', function () { console.log('d') }) h1.intercept({ call: (...args) => { console.log(...args, '-------------intercept call'); }, // register: (tap) => { console.log(tap, '------------------intercept register'); return tap; }, loop: (...args) => { console.log(...args, '-------------intercept loop') }, tap: (tap) => { console.log(tap, '-------------------intercept tap') } }) h1.call(6);
new SyncHook(['synchook'])
首先先建立一個同步鉤子對象,那這一步會幹什麼呢?算法
這一步會先執行超類Hook的初始化工做後端
// 初始化 constructor(args) { // 參數必須是數組 if (!Array.isArray(args)) args = []; // 把數組參數賦值給 _args 內部屬性, new 的時候傳進來的一系列參數. this._args = args; // 綁定taps,應該是事件 this.taps = []; // 攔截器數組 this.interceptors = []; // 暴露出去用於調用同步鉤子的函數 this.call = this._call; // 暴露出去的用於調用異步promise函數 this.promise = this._promise; // 暴露出去的用於調用異步鉤子函數 this.callAsync = this._callAsync; // 用於生成調用函數的時候,保存鉤子數組的變量,如今暫時先無論. this._x = undefined; }
.tap()
如今咱們來看看調用了tap() 方法後發生了什麼數組
tap(options, fn) { // 下面是一些參數的限制,第一個參數必須是字符串或者是帶name屬性的對象, // 用於標明鉤子,並把鉤子和名字都整合到 options 對象裏面 if (typeof options === "string") options = { name: options }; if (typeof options !== "object" || options === null) throw new Error( "Invalid arguments to tap(options: Object, fn: function)" ); options = Object.assign({ type: "sync", fn: fn }, options); if (typeof options.name !== "string" || options.name === "") throw new Error("Missing name for tap"); // 註冊攔截器 options = this._runRegisterInterceptors(options); // 插入鉤子 this._insert(options); }
_runRegisterInterceptors(options) { // 如今這個參數應該是這個樣子的{fn: function..., type: sync,name: 'A' } // 遍歷攔截器,有就應用,沒有就把配置返還回去 for (const interceptor of this.interceptors) { if (interceptor.register) { // 把選項傳入攔截器註冊,從這裏能夠看出,攔截器的register 能夠返回一個新的options選項,而且替換掉原來的options選項,也就是說能夠在執行了一次register以後 改變你當初 tap 進去的方法 const newOptions = interceptor.register(options); if (newOptions !== undefined) options = newOptions; } } return options; }
注意: 這裏執行的register攔截器是有順序問題的, 這個執行在tap()裏面,也就是說,你這個攔截器要在調用tap(),以前就調用 intercept()添加的.promise
那攔截器是怎麼添加進去的呢,來看下intercept()緩存
intercept(interceptor) { // 重置全部的 調用 方法,在教程中咱們提到了 編譯出來的調用方法依賴的其中一點就是 攔截器. 全部每添加一個攔截器都要重置一次調用方法,在下一次編譯的時候,從新生成. this._resetCompilation(); // 保存攔截器 並且是複製一份,保留本來的引用 this.interceptors.push(Object.assign({}, interceptor)); // 運行全部的攔截器的register函數而且把 taps[i],(tap對象) 傳進去. // 在intercept 的時候也會遍歷執行一次當前全部的taps,把他們做爲參數調用攔截器的register,而且把返回的 tap對象(tap對象就是指 tap函數裏面把fn和name這些信息整合起來的那個對象) 替換了原來的 tap對象,因此register最好返回一個tap, 在例子中我返回了原來的tap, 可是其實最好返回一個全新的tap if (interceptor.register) { for (let i = 0; i < this.taps.length; i++) this.taps[i] = interceptor.register(this.taps[i]); } }
注意: 也就是在調用tap() 以後再傳入的攔截器,會在傳入的時候就爲每個tap 調用register方法閉包
_insert(item) { // 重置資源,由於每個插件都會有一個新的Compilation this._resetCompilation(); // 順序標記, 這裏聯合 __test__ 包裏的Hook.js一塊兒使用 // 看源碼不懂,能夠看他的測試代碼,就知道他寫的是什麼目的. // 從測試代碼能夠看到,這個 {before}是插件的名字. let before; // before 能夠是單個字符串插件名稱,也能夠是一個字符串數組插件. if (typeof item.before === "string") { before = new Set([item.before]); } else if (Array.isArray(item.before)) { before = new Set(item.before); } // 階段 // 從測試代碼能夠知道這個也是一個控制順序的屬性,值越小,執行得就越在前面 // 並且優先級低於 before let stage = 0; if (typeof item.stage === "number") stage = item.stage; let i = this.taps.length; // 遍歷全部`tap`了的函數,而後根據 stage 和 before 進行從新排序. // 假設如今tap了 兩個鉤子 A B `B` 的配置是 {name: 'B', before: 'A'} while (i > 0) {// i = 1, taps = [A] i--;// i = 0 首先-- 是由於要從最後一個開始 const x = this.taps[i];// x = A this.taps[i + 1] = x;// i = 0, taps[1] = A i+1 把當前元素日後移位,把位置讓出來 const xStage = x.stage || 0;// xStage = 0 if (before) {// 若是有這個屬性就會進入這個判斷 if (before.has(x.name)) {// 若是before 有x.name 就會把這個插件名稱從before這個列表裏刪除,表明這個鉤子位置已經在當前的鉤子以前 before.delete(x.name); continue;// 若是before還有元素,繼續循環,執行上面的操做 } if (before.size > 0) { continue;// 若是before還有元素,那就一直循環,直到第一位. } } if (xStage > stage) {// 若是stage比當前鉤子的stage大,繼續往前挪 continue; } i++; break; } this.taps[i] = item;// 把挪出來的位置插入傳進來的鉤子 }
這其實就是一個排序算法, 根據before, stage 的值來排序,也就是說你能夠這樣tap進來一個插件架構
h1.tap({ name: 'B', before: 'A' }, () => { console.log('i am B') })
發佈訂閱模式是一個在先後端都盛行的一個模式,前端的promise,事件,等等都基於發佈訂閱模式,其實tapable 也是一種發佈訂閱模式,上面的tap 只是訂閱了鉤子函數,咱們還須要發佈他,接下來咱們談談h1.call()
,跟緊了,這裏面纔是重點.
咱們能夠在初始化中看到this.call = this._call
,那咱們來看一下 this._call() 是個啥
Object.defineProperties(Hook.prototype, { _call: { value: createCompileDelegate("call", "sync"), configurable: true, writable: true }, _promise: { value: createCompileDelegate("promise", "promise"), configurable: true, writable: true }, _callAsync: { value: createCompileDelegate("callAsync", "async"), configurable: true, writable: true } });
結果很明顯,這個函數是由createCompileDelegate(),這個函數返回的,依賴於,函數的名字以及鉤子的類型.
createCompileDelegate(name, type)
function createCompileDelegate(name, type) { return function lazyCompileHook(...args) { // 子類調用時,this默認綁定到子類 // (不明白的能夠了解js this指向,一個函數的this指向調用他的對象,沒有就是全局,除非使用call apply bind 等改變指向) // 在咱們的例子中,這個 this 是 SyncHook this[name] = this._createCall(type); // 用args 去調用Call return this[name](...args); }; }
在上面的註釋上能夠加到,他經過閉包保存了name
跟type
的值,在咱們這個例子中,這裏就是this.call = this._createCall('sync');
而後把咱們外部調用call(666) 時 傳入的參數給到他編譯生成的方法中.
注意,在咱們這個例子當中我在call的時候並無傳入參數.
這時候這個call
方法的重點就在_createCall
方法裏面了.
_createCall(type) { // 傳遞一個整合了各個依賴條件的對象給子類的compile方法 return this.compile({ taps: this.taps, interceptors: this.interceptors, args: this._args, type: type }); }
從一開始,咱們就在Hook.js上分析,咱們來看看Hook上的compile
compile(options) { throw new Error("Abstract: should be overriden"); }
清晰明瞭,這個方法必定要子類複寫,否則報錯,上面的_createCompileDelegate
的註釋也寫得很清楚,在當前的上下文中,this指向的是,子類,在咱們這個例子中就是SyncHook
SyncHook
的compilecompile(options) { // 如今options 是由Hook裏面 傳到這裏的 // options // { // taps: this.taps, tap對象數組 // interceptors: this.interceptors, 攔截器數組 // args: this._args, // type: type // } // 對應回教程中的編譯出來的調用函數依賴於的那幾項看看,是否是這些,鉤子的個數,new SyncHook(['arg'])的參數個數,攔截器的個數,鉤子的類型. factory.setup(this, options); return factory.create(options); }
好吧 如今來看看setup, 咦? factory 怎麼來的,原來
const factory = new SyncHookCodeFactory();
是new 出來的
constructor(config) { // 這個config做用暫定.由於我看了這個文件,沒看到有引用的地方, // 應該是其餘子類有引用到 this.config = config; // 這兩個不難懂, 往下看就知道了 this.options = undefined; this._args = undefined; }
setup(instance, options) { // 這裏的instance 是syncHook 實例, 其實就是把tap進來的鉤子數組給到鉤子的_x屬性裏. instance._x = options.taps.map(t => t.fn); }
OK, 到create了
create(options) { // 初始化參數,保存options到本對象this.options,保存new Hook(["options"]) 傳入的參數到 this._args this.init(options); let fn; // 動態構建鉤子,這裏是抽象層,分同步, 異步, promise switch (this.options.type) { // 先看同步 case "sync": // 動態返回一個鉤子函數 fn = new Function( // 生成函數的參數,no before no after 返回參數字符串 xxx,xxx 在 // 注意這裏this.args返回的是一個字符串, // 在這個例子中是options this.args(), '"use strict";\n' + this.header() + this.content({ onError: err => `throw ${err};\n`, onResult: result => `return ${result};\n`, onDone: () => "", rethrowIfPossible: true }) ); break; case "async": fn = new Function( this.args({ after: "_callback" }), '"use strict";\n' + this.header() + // 這個 content 調用的是子類類的 content 函數, // 參數由子類傳,實際返回的是 this.callTapsSeries() 返回的類容 this.content({ onError: err => `_callback(${err});\n`, onResult: result => `_callback(null, ${result});\n`, onDone: () => "_callback();\n" }) ); break; case "promise": let code = ""; code += '"use strict";\n'; code += "return new Promise((_resolve, _reject) => {\n"; code += "var _sync = true;\n"; code += this.header(); code += this.content({ onError: err => { let code = ""; code += "if(_sync)\n"; code += `_resolve(Promise.resolve().then(() => { throw ${err}; }));\n`; code += "else\n"; code += `_reject(${err});\n`; return code; }, onResult: result => `_resolve(${result});\n`, onDone: () => "_resolve();\n" }); code += "_sync = false;\n"; code += "});\n"; fn = new Function(this.args(), code); break; } // 把剛纔init賦的值初始化爲undefined // this.options = undefined; // this._args = undefined; this.deinit(); return fn; }
到了這個方法,一切咱們都一目瞭然了(看content的參數), 在咱們的例子中他是經過動態的生成一個call方法,根據的條件有,鉤子是否有context 屬性(這個是根據header的代碼才能知道), 鉤子的個數, 鉤子的類型,鉤子的參數,鉤子的攔截器個數.
注意,這上面有關於 fn這個變量的函數,返回的都是字符串,不是函數不是方法,是返回能夠轉化成代碼執行的字符串,思惟要轉變過來.
如今咱們來看看header()
header() { let code = ""; // this.needContext() 判斷taps[i] 是否 有context 屬性, 任意一個tap有 都會返回 true if (this.needContext()) { // 若是有context 屬性, 那_context這個變量就是一個空的對象. code += "var _context = {};\n"; } else { // 不然 就是undefined code += "var _context;\n"; } // 在setup()中 把全部tap對象的鉤子 都給到了 instance ,這裏的this 就是setup 中的instance _x 就是鉤子對象數組 code += "var _x = this._x;\n"; // 若是有攔截器,在咱們的例子中,就有一個攔截器 if (this.options.interceptors.length > 0) { // 保存taps 數組到_taps變量, 保存攔截器數組 到變量_interceptors code += "var _taps = this.taps;\n"; code += "var _interceptors = this.interceptors;\n"; } // 若是沒有攔截器, 這裏也不會執行.一個攔截器只會生成一次call // 在咱們的例子中,就有一個攔截器,就有call for (let i = 0; i < this.options.interceptors.length; i++) { const interceptor = this.options.interceptors[i]; if (interceptor.call) { // getInterceptor 返回的 是字符串 是 `_interceptors[i]` // 後面的before 由於咱們的攔截器沒有context 因此返回的是undefined 因此後面沒有跟一個空對象 code += `${this.getInterceptor(i)}.call(${this.args({ before: interceptor.context ? "_context" : undefined })});\n`; } } return code; // 注意 header 返回的不是代碼,是能夠轉化成代碼的字符串(這個時候並無執行). /** * 此時call函數應該爲: * "use strict"; * function (options) { * var _context; * var _x = this._x; * var _taps = this.taps; * var _interterceptors = this.interceptors; * // 咱們只有一個攔截器因此下面的只會生成一個 * _interceptors[0].call(options); *} */ }
如今到咱們的this.content()
了,仔細一看,this.content()
方法並不在HookCodeFactory
上,很明顯這個content是由子類來實現的,往回看看這個create是由誰調用的?沒錯,是SuncHookCodeFactory的石料理,咱們來看看SyncHook.js
上的SyncHookCodeFactory
實現的content
在看這個content實現以前,先來回顧一下父類的create()
給他傳了什麼參數.
this.content({ onError: err => `throw ${err};\n`, onResult: result => `return ${result};\n`, onDone: () => "", rethrowIfPossible: true })
注意了,這上面不是拋出錯誤,不是返回值. 這裏面的回調執行了之後返回的是一個字符串,不要搞混了代碼與能夠轉化成代碼的字符串.
content({ onError, onResult, onDone, rethrowIfPossible }) { return this.callTapsSeries({ // 能夠在這改變onError 可是這裏的 i 並無用到,這是什麼操做... // 注意這裏並無傳入onResult onError: (i, err) => onError(err), onDone, // 這個默認爲true rethrowIfPossible }); }
這個函數返回什麼取決於this.callTapSeries(), 那接下來咱們來看看這個函數(這層層嵌套,其實也是有可斟酌的地方.看源碼不只要看實現,代碼的組織也是很重要的編碼能力)
剛纔函數的頭部已經出來了,頭部作了初始化的操做,與生成執行攔截器代碼.content很明顯,要開始生成執行咱們的tap對象的代碼了(若是否則,咱們的tap進來的函數在哪裏執行呢? 滑稽:).
callTapsSeries({ onError, onResult, onDone, rethrowIfPossible }) { // 若是 taps 鉤子處理完畢,執行onDone,或者一個tap都沒有 onDone() 返回的是一個字符串.看上面的回顧就知道了. if (this.options.taps.length === 0) return onDone(); // 若是由異步鉤子,把第一個異步鉤子的下標,若是沒有這個返回的是-1 const firstAsync = this.options.taps.findIndex(t => t.type !== "sync"); // 定義一個函數 接受一個 number 類型的參數, i 應該是taps的index // 從這個函數的命名來看,這個函數應該會遞歸的執行 // 咱們先開最後的return語句,發現第一個傳進來的參數是0 const next = i => { // 若是 大於等於鉤子函數數組長度, 返回並執行onDone回調,就是tap對象都處理完了 // 跳出遞歸的條件 if (i >= this.options.taps.length) { return onDone(); } // 這個方法就是遞歸的關鍵,看見沒,逐漸往上遍歷 // 注意這裏只是定義了方法,並無執行 const done = () => next(i + 1); // 傳入一個值 若是是false 就執行onDone true 返回一個 "" // 字面意思,是否跳過done 應該是增長一個跳出遞歸的條件 const doneBreak = skipDone => { if (skipDone) return ""; return onDone(); }; // 這裏就是處理單個taps對象的關鍵,傳入一個下標,和一系列回調. return this.callTap(i, { // 調用的onError 是 (i, err) => onError(err) , 後面這個onError(err)是 () => `throw ${err}` // 目前 i done doneBreak 都沒有用到 onError: error => onError(i, error, done, doneBreak), // 這裏onResult 同步鉤子的狀況下在外部是沒有傳進來的,剛纔也提到了 // 這裏onResult是 undefined onResult: onResult && (result => { return onResult(i, result, done, doneBreak); }), // 沒有onResult 必定要有一個onDone 因此這裏就是一個默認的完成回調 // 這裏的done 執行的是next(i+1), 也就是迭代的處理完全部的taps onDone: !onResult && (() => {return done();}), // rethrowIfPossible 默認是 true 也就是返回後面的 // 由於沒有異步函數 firstAsync = -1. // 因此返回的是 -1 < 0,也就是true, 這個能夠判斷當前的是不是異步的tap對象 // 這裏挺妙的 若是是 false 那麼當前的鉤子類型就不是sync,多是promise或者是async // 具體做用要看callTaps()如何使用這個. rethrowIfPossible: rethrowIfPossible && (firstAsync < 0 || i < firstAsync) }); }; return next(0); }
callTap()
了.callTap
挺長的,由於他也分了3種類型分別處理,像create()同樣.
/** tapIndex 下標 * onError:() => onError(i,err,done,skipdone) , * onReslt: undefined * onDone: () => {return: done()} //開啓遞歸的鑰匙 * rethrowIfPossible: false 說明當前的鉤子不是sync的. */ callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) { let code = ""; // hasTapCached 是否有tap的緩存, 這個要看看他是怎麼作的緩存了 let hasTapCached = false; // 這裏仍是攔截器的用法,若是有就執行攔截器的tap函數 for (let i = 0; i < this.options.interceptors.length; i++) { const interceptor = this.options.interceptors[i]; if (interceptor.tap) { if (!hasTapCached) { // 這裏getTap返回的是 _taps[0] _taps[1]... 的字符串 // 這裏生成的代碼就是 `var _tap0 = _taps[0]` // 注意: _taps 變量咱們在 header 那裏已經生成了 code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`; // 能夠看到這個變量的做用就是,若是有多個攔截器.這裏也只會執行一次. // 注意這句獲取_taps 對象的下標用的是tapIndex,在一次循環中,這個tapIndex不會變 // 就是說若是這裏執行屢次,就會生成多個重複代碼,不穩定,也影響性能. // 可是你又要判斷攔截器有沒有tap才能夠執行,或許有更好的寫法 // 若是你能想到,那麼你就是webpack的貢獻者了.不過這樣寫,彷佛也沒什麼很差. hasTapCached = true; } // 這裏很明顯跟上面的getTap 同樣 返回的都是字符串 // 我就直接把這裏的code 分析出來了,注意 這裏仍是在循壞中. // code += _interceptor[0].tap(_tap0); // 因爲咱們的攔截器沒有context,因此沒傳_context進來. // 能夠看到這裏是調用攔截器的tap方法而後傳入tap0對象的地方 code += `${this.getInterceptor(i)}.tap(${ interceptor.context ? "_context, " : "" }_tap${tapIndex});\n`; } } // 跑出了循壞 // 這裏的getTapFn 返回的也是字符串 `_x[0]` // callTap用到的這些所有在header() 那裏生成了,忘記的回頭看一下. // 這裏的code就是: var _fn0 = _x[0] code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`; const tap = this.options.taps[tapIndex]; // 開始處理tap 對象 switch (tap.type) { case "sync": // 全是同步的時候, 這裏不執行, 若是有異步函數,那麼恭喜,有可能會報錯.因此他加了個 try...catch if (!rethrowIfPossible) { code += `var _hasError${tapIndex} = false;\n`; code += "try {\n"; } // 前面分析了 同步的時候 onResult 是 undefined // 咱們也分析一下若是走這裏會怎樣 // var _result0 = _fn0(option) // 能夠看到是調用tap 進來的鉤子而且接收參數 if (onResult) { code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({ before: tap.context ? "_context" : undefined })});\n`; } else { // 因此會走這裏 // _fn0(options) 額... 我日 有就接受一下結果 code += `_fn${tapIndex}(${this.args({ before: tap.context ? "_context" : undefined })});\n`; } // 把 catch 補上,在這個例子中沒有 if (!rethrowIfPossible) { code += "} catch(_err) {\n"; code += `_hasError${tapIndex} = true;\n`; code += onError("_err"); code += "}\n"; code += `if(!_hasError${tapIndex}) {\n`; } // 有onResult 就把結果給傳遞出去. 目前沒有 if (onResult) { code += onResult(`_result${tapIndex}`); } // 有onDone() 就調用他開始遞歸,還記得上面的next(i+1) 嗎? if (onDone) { code += onDone(); } // 這裏是不上上面的if的大括號,在這個例子中沒有,因此這裏也不執行 if (!rethrowIfPossible) { code += "}\n"; } // 同步狀況下, 這裏最終的代碼就是 // var _tap0 = _taps[0]; // _interceptors[0].tap(_tap0); // var _fn0 = _x[0]; // _fn0(options); // 能夠看到,這裏會遞歸下去 // 由於咱們tap了4個鉤子 // 因此這裏會從復4次 // 最終長這樣 // var _tap0 = _taps[0]; // _interceptors[0].tap(_tap0); // var _fn0 = _x[0]; // _fn0(options); // var _tap1 = _taps[1]; // _interceptors[1].tap(_tap1); // var _fn1 = _x[1]; // _fn1(options); // ...... break; case "async": let cbCode = ""; if (onResult) cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`; else cbCode += `_err${tapIndex} => {\n`; cbCode += `if(_err${tapIndex}) {\n`; cbCode += onError(`_err${tapIndex}`); cbCode += "} else {\n"; if (onResult) { cbCode += onResult(`_result${tapIndex}`); } if (onDone) { cbCode += onDone(); } cbCode += "}\n"; cbCode += "}"; code += `_fn${tapIndex}(${this.args({ before: tap.context ? "_context" : undefined, after: cbCode })});\n`; break; case "promise": code += `var _hasResult${tapIndex} = false;\n`; code += `var _promise${tapIndex} = _fn${tapIndex}(${this.args({ before: tap.context ? "_context" : undefined })});\n`; code += `if (!_promise${tapIndex} || !_promise${tapIndex}.then)\n`; code += ` throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise${tapIndex} + ')');\n`; code += `_promise${tapIndex}.then(_result${tapIndex} => {\n`; code += `_hasResult${tapIndex} = true;\n`; if (onResult) { code += onResult(`_result${tapIndex}`); } if (onDone) { code += onDone(); } code += `}, _err${tapIndex} => {\n`; code += `if(_hasResult${tapIndex}) throw _err${tapIndex};\n`; code += onError(`_err${tapIndex}`); code += "});\n"; break; } return code; }
好了, 到了這裏 咱們能夠把compile 出來的call 方法輸出出來了
"use strict"; function (options) { var _context; var _x = this._x; var _taps = this.taps; var _interterceptors = this.interceptors; // 咱們只有一個攔截器因此下面的只會生成一個 _interceptors[0].call(options); var _tap0 = _taps[0]; _interceptors[0].tap(_tap0); var _fn0 = _x[0]; _fn0(options); var _tap1 = _taps[1]; _interceptors[1].tap(_tap1); var _fn1 = _x[1]; _fn1(options); var _tap2 = _taps[2]; _interceptors[2].tap(_tap2); var _fn2 = _x[2]; _fn2(options); var _tap3 = _taps[3]; _interceptors[3].tap(_tap3); var _fn3 = _x[3]; _fn3(options); }
到了這裏能夠知道,咱們的例子中h1.call()
其實調用的就是這個方法.到此咱們能夠說是知道了這個庫的百分之80了.
不知道你們有沒有發現,這個生成的函數的參數列表是從哪裏來的呢?往回翻到create()方法裏面調用的this.args()
你就會看見,沒錯就是this._args. 這個東西在哪裏初始化呢? 翻一下就知道,這是在Hook.js
這個類裏面初始化的,也就是說你h1 = new xxxHook(['options'])
的時候傳入的數組有幾個值,那麼你h1.call({name: 'haha'})
就能傳幾個值.看教程的時候他說,這裏傳入的是一個參數名字的字符串列表,那時候我就納悶,什麼鬼,我傳入的不是值嗎,怎麼就變成了參數名稱,如今徹底掌握....
好了,最簡單的SyncHook
已經搞掂,可是一看tapable
內部核心使用的鉤子卻不是他,而是SyncBailHook
,在教程中咱們已經知道,bail
是隻要有一個鉤子執行完了,而且返回一個值,那麼其餘的鉤子就不執行.咱們來看看他是怎麼實現的.
從剛纔咱們弄明白的synchook
,咱們知道了他的套路,其實生成的函數的header()
都是同樣的,此次咱們直接來看看bailhook
實現的content()
方法
content({ onError, onResult, onDone, rethrowIfPossible }) { return this.callTapsSeries({ onError: (i, err) => onError(err), // 看回callTapsSeries 就知道這裏傳入的next 是 done onResult: (i, result, next) => `if(${result} !== undefined) {\n${onResult( result )};\n} else {\n${next()}}\n`, onDone, rethrowIfPossible }); }
看出來了哪裏不同嗎? 是的bailhook
的 callTapsSeries
傳了onResult
屬性,咱們來看看他這個onResult是啥黑科技
父類傳的onResult
默認是 (result) => 'return ${result}'
,那麼他這裏返回的就是:
// 下面返回的是字符串, if (xxx !== undefined) { // 這裏說明,只要有返回值(由於不返回默認是undefined),就會當即return; return result; } else { // next(); 這裏返回的是一個字符串(由於要生成字符串代碼) // 我在上面的註釋中提到了 next 是 done 就是那個開啓遞歸的門 // 因此若是tap 一直沒返回值, 這裏就會一直 if...else.. 的嵌套下去 }
回頭想一想,咱們剛剛是否是分析了capTap()
,若是咱們傳了onResult
會怎樣? 若是你還記得就知道,若是有傳了onResult
這個回調,他就會接收這個返回值.而且會調用這個回調把result
傳出去.
並且還要注意的是,onDone
在callTap()
的時候是處理過的,我在貼出來一次.
onDone:!onResult && (() => {return done();})
也就是說若是我傳了onResult
那麼這個onDone
就是一個false
.
因此遞歸的門如今從sync
的onDone
,變到syncBail
的onResult
了
好,如今帶着這些變化去看this.capTap()
,你就能推出如今這個 call 函數會變成這樣.
"use strict"; function (options) { var _context; var _x = this._x; var _taps = this.taps; var _interterceptors = this.interceptors; // 咱們只有一個攔截器因此下面的只會生成一個 _interceptors[0].call(options); var _tap0 = _taps[0]; _interceptors[0].tap(_tap0); var _fn0 = _x[0]; var _result0 = _fn0(options); if (_result0 !== undefined) { // 這裏說明,只要有返回值(由於不返回默認是undefined),就會當即return; return _result0 } else { var _tap1 = _taps[1]; _interceptors[1].tap(_tap1); var _fn1 = _x[1]; var _result1 = _fn1(options); if (_result1 !== undefined) { return _result1 } else { var _tap2 = _taps[2]; _interceptors[2].tap(_tap2); var _fn2 = _x[2]; var _result2 = _fn2(options); if (_result2 !== undefined) { return _result2 } else { var _tap3 = _taps[3]; _interceptors[3].tap(_tap3); var _fn3 = _x[3]; _fn3(options); } } }
到現在,tapable庫 已經刪除了 tapable.js文件(可能作了一些整合,更細分了),只留下了鉤子文件.但不影響功能,webpack 裏的compile
compilation
等一衆重要插件,都是基於 tapable庫中的這些鉤子.
如今咱們require('tapable')獲得的對象是這樣的:
{ SyncHook: function(...){}, SyncBailHook: function(...){}, ... }
到此,關於tapable的大部分我都解剖了一遍,還有其餘類型的hook
若是大家願意,相信大家去研究一下,也可以遊刃有餘.
那個,寫得有些隨性,可能會讓大家以爲模糊,可是...我真盡力了,這篇改了幾遍,歷時一個星期...,不懂就在那個評論區問我.我看到會回覆的.共勉.
後記:
原本覺得會很難,可是越往下深刻的時候發現,大神之因此成爲大神,不是他的代碼寫得牛,是他的思惟牛,沒有看不懂的代碼,只有跟不上的思路,要看懂他如何把call 函數組織出來不難,難的是,他竟然能想到這樣來生成函數,還能夠考慮到,攔截器鉤子,和context
屬性,以及他的 onResult
onDone
回調的判斷,架構的設計,等等,一步接一步.先膜拜吧...
路漫漫其修遠兮, 吾將上下而求索.