- src - sim ---- 簡單的模擬實現 - /.js$/ ---- 使用
Webpack 就像一條生產線, 要通過一系列的處理流程才能將源文件轉換成輸出結果。這條生產線上的每一個流程都是單一的, 多個流程之間存在依賴關係。只能完成當前處理後纔會轉交到下一個流程。javascript
插件就像一個插入到生產線中的一個功能, 它會在特定的時機對生產線上的資源進行處理。html
這條生產線很複雜, Webpack則是經過 tapable
核心庫來組織這條生產線。java
Webpack 在運行中會經過 tapable
提供的鉤子進行廣播事件, 插件只須要監聽它關心的事件,就能夠加入到這條生產線中,去改變生產線的運做。使得 Webpack總體擴展性很好。webpack
Tapable 提供同步(Sync)和異步(Async)鉤子類。而異步又分爲 異步串行
、異步並行
鉤子類。git
逐個分析每一個鉤子類的使用及其原理github
同步鉤子類經過實例的 tap
方法監聽函數, 經過 call
發佈事件web
同步串行不關心訂閱函數執行後的返回值是什麼。其原理是將監聽(訂閱)的函數存放到一個數組中, 發佈時遍歷數組中的監聽函數而且將發佈時的 arguments
傳遞給監聽函數數組
class SyncHook { constructor(options) { this.options = options this.hooks = [] //存放監聽函數的數組 } tap(name, callback) { this.hooks.push(callback) } call(...args) { for (let i = 0; i < this.hooks.length; i++) { this.hooks[i](...args) } } } const synchook = new SyncHook('name') // 註冊監聽函數 synchook.tap('name', (data) => { console.log('name', data) }) synchook.tap('age', (data) => { console.log('age', data) }) // 發佈事件 synchook.call('qiqingfu')
打印結果:promise
name qiqingfu age qiqingfu
同步串行, 可是若是監聽函數的返回值不爲 null
, 就終止後續的監聽函數執行異步
class SyncBailHook { constructor(options) { this.options = options this.hooks = [] } tap(name, callback) { this.hooks.push(callback) } call(...args) { let ret, i = 0 do { // 將第一個函數的返回結果賦值給ret, 在while中若是結果爲 true就繼續執行do代碼塊 ret = this.hooks[i++](...args) } while(!ret) } } const syncbailhook = new SyncBailHook('name') syncbailhook.tap('name', (data) => { console.log('name', data) return '個人返回值不爲null' }) syncbailhook.tap('age', (data) => { console.log('age', data) }) syncbailhook.call('qiqingfu')
執行結果
name qiqingfu
同步串行瀑布流, 瀑布流指的是第一個監聽函數的返回值,作爲第二個監聽函數的參數。第二個函數的返回值做爲第三個監聽函數的參數,依次類推...
class SyncWaterfallHook { constructor(options) { this.options = options this.hooks = [] } tap(name, callback) { this.hooks.push(callback) } call(...args) { let [firstHook, ...otherHooks] = this.hooks /** * 經過解構賦值先取出第一個監聽函數執行 * 而且將第一個函數的執行結果傳遞給第二個, 第二個傳遞給第三個,迭代的過程 */ let ret = firstHook(...args) otherHooks.reduce((f,n) => { return n(f) }, ret) } } const syncWaterfallHook = new SyncWaterfallHook('name') syncWaterfallHook.tap('name', data => { console.log('name', data) return 23 }) syncWaterfallHook.tap('age', data => { console.log('age', data) }) syncWaterfallHook.call('qiqingfu')
打印結果
name qiqingfu age 23
同步串行, 若是監聽函數的返回值爲 true
, 則反覆執行當前的監聽函數,直到返回指爲 undefind
則繼續執行下面的監聽函數
class SyncLoopHook { constructor(options) { this.options = options this.hooks = [] } tap(name, callback) { this.hooks.push(callback) } call(...args) { for (let i = 0; i < this.hooks.length; i++) { let hook = this.hooks[i], ret do{ ret = hook(...args) }while(ret === true && ret !== undefined) } } } const syncLoopHook = new SyncLoopHook('name') let n1 = 0 syncLoopHook.tap('name', data => { console.log('name', data) return n1 < 2 ? true : undefined }) syncLoopHook.tap('end', data => { console.log('end', data) }) syncLoopHook.call('qiqingfu')
執行結果
name qiqingfu name qiqingfu name qiqingfu 第三次打印的時候, n1的指爲2, 返回值爲 undefined則執行後面的監聽函數 end qiqingfu
(Parallel)
(Series)
凡是有異步,必有回調
同步鉤子是經過 tap
來監聽函數的, call
來發布的。
異步鉤子是經過 tapAsync
或 tapPromise
來監聽函數,經過 callAsync
或 promise
來發布訂閱的。
異步並行, 監聽的函數會一塊執行, 哪一個函數先執行完就先觸發。不須要關心監聽函數的返回值。
class AsyncParallelHook { constructor(options) { this.options = options this.asyncHooks = [] } // 訂閱 tapAsync(name, callback) { this.asyncHooks.push(callback) } // 發佈 callAsync(...args) { /** * callAsync(arg1, arg2,..., cb) * 發佈的時候最後一個參數能夠是回調函數 * 訂閱的每個函數的最後一個參數也是一個回調函數,全部的訂閱函數執行完 * 且都調用了最後一個函數,纔會執行cb */ const finalCallback = args.pop() let i = 0 // 將這個做爲最後一個參數傳過去,使用的時候選擇性調用 const done = () => { ++i === this.asyncHooks.length && finalCallback() } this.asyncHooks.forEach(hook => { hook(...args, done) }) } } const asyncParallelHook = new AsyncParallelHook('name') asyncParallelHook.tapAsync('name', (data, done) => { setTimeout(() => { console.log('name', data) done() }, 2000) }) asyncParallelHook.tapAsync('age', (data, done) => { setTimeout(() => { console.log('age', data) done() }, 3000) }) console.time('time') asyncParallelHook.callAsync('qiqingfu', () => { console.log('監聽函數都調用了 done') console.timeEnd('time') })
打印結果
name qiqingfu age qiqingfu 監聽函數都調用了 done time: 3002.691ms
暫時不理解
異步串行鉤子類, 不關心 callback
的參數。異步函數一個一個的執行,可是必須調用 done函數。
class AsyncSeriesHook { constructor(options) { this.options = options this.asyncHooks = [] } tapAsync(name, callback) { this.asyncHooks.push(callback) } callAsync(...args) { const finalCallback = args.pop() let i = 0 const done = () => { let task = this.asyncHooks[i++] task ? task(...args, done) : finalCallback() } done() } } const asyncSeriesHook = new AsyncSeriesHook('name') asyncSeriesHook.tapAsync('name', (data, done) => { setTimeout(() => { console.log('name', data) done() }, 1000) }) asyncSeriesHook.tapAsync('age', (data, done) => { setTimeout(() => { console.log('age', data) done() }, 2000) }) console.time('time') asyncSeriesHook.callAsync('qiqingfu', () => { console.log('end') console.timeEnd('time') })
執行結果
name qiqingfu age qiqingfu end time: 3010.915ms
同步串行鉤子類, callback的參數若是不是 null
, 後面全部的異步函數都不會執行,直接執行 callAsync
方法的回調函數
class AsyncSeriesBailHook { constructor(options) { this.options = options this.asyncHooks = [] } tapAsync(name, callback) { this.asyncHooks.push(callback) } callAsync(...args) { const finalCallback = args.pop() let i = 0 const done = data => { if (data) return finalCallback() let task = this.asyncHooks[i++] task ? task(...args, done) : finalCallback() } done() } } const asyncSeriesBailHook = new AsyncSeriesBailHook('name') asyncSeriesBailHook.tapAsync('1', (data, done) => { setTimeout(() => { console.log('1', data) done(null) }, 1000) }) asyncSeriesBailHook.tapAsync('2', (data, done) => { setTimeout(() => { console.log('2', data) done(null) }, 2000) }) console.time('times') asyncSeriesBailHook.callAsync('qiqingfu', () => { console.log('end') console.timeEnd('times') })
打印結果
1 qiqingfu 2 qiqingfu end times: 3012.060ms
同步串行鉤子類, 上一個監聽函數 callback(err, data)的第二個參數, 能夠做爲下一個監聽函數的參數
class AsyncSeriesWaterfallHook { constructor(options) { this.options = options this.asyncHooks = [] } tapAsync(name, callback) { this.asyncHooks.push(callback) } callAsync(...args) { const finalCallback = args.pop() let i = 0, once const done = (err, data) => { let task = this.asyncHooks[i++] if (!task) return finalCallback() if (!once) { // 只執行一次 task(...args, done) once = true } else { task(data, done) } } done() } } const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook('name') asyncSeriesWaterfallHook.tapAsync('1', (data, done) => { setTimeout(() => { console.log('1', data) done(null, '第一個callback傳遞的參數') }, 1000) }) asyncSeriesWaterfallHook.tapAsync('2', (data, done) => { setTimeout(() => { console.log('2', data) done(null) }, 1000) }) console.time('timer') asyncSeriesWaterfallHook.callAsync('qiqingfu', () => { console.log('end') console.timeEnd('timer') })
打印結果
1 qiqingfu 2 第一個callback傳遞的參數 end timer: 2015.445ms
若是理解有誤, 麻煩糾正!