directory
- src
- sim ---- 簡單的模擬實現
- /.js$/ ---- 使用
程式碼已上傳github, 地址
Detailed
Webpack 就像一條生產線, 要經過一系列的處理流程才能將原始檔轉換成輸出結果。這條生產線上的每個流程都是單一的, 多個流程之間存在依賴關係。只能完成當前處理後才會轉交到下一個流程。
外掛就像一個插入到生產線中的一個功能, 它會在特定的時機對生產線上的資源進行處理。
這條生產線很複雜, Webpack則是通過 tapable
核心庫來組織這條生產線。
Webpack 在執行中會通過 tapable
提供的鉤子進行廣播事件, 外掛只需要監聽它關心的事件,就可以加入到這條生產線中,去改變生產線的運作。使得 Webpack整體擴充套件性很好。
Tapable Hook
Tapable 提供同步(Sync)和非同步(Async)鉤子類。而非同步又分為 非同步序列
、非同步並行
鉤子類。
右鍵圖片,在新標籤中檢視完整圖片
逐個分析每個鉤子類的使用及其原理
同步鉤子類
- SyncHook
- SyncBailHook
- SyncWaterfallHook
- SyncLoopHook
同步鉤子類通過例項的 tap
方法監聽函式, 通過 call
釋出事件
SyncHook
同步序列不關心訂閱函式執行後的返回值是什麼。其原理是將監聽(訂閱)的函式存放到一個陣列中, 釋出時遍歷陣列中的監聽函式並且將釋出時的 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`)
列印結果:
name qiqingfu
age qiqingfu
SyncBailHook
同步序列, 但是如果監聽函式的返回值不為 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
SyncWaterfallHook
同步序列瀑布流, 瀑布流指的是第一個監聽函式的返回值,做為第二個監聽函式的引數。第二個函式的返回值作為第三個監聽函式的引數,依次類推…
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
SyncLoopHook
同步序列, 如果監聽函式的返回值為 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)
- AsyncParallelHook
- AsyncParalleBailHook
- 非同步序列
(Series)
- AsyncSeriesHook
- AsyncSeriesBailHook
- AsyncSeriesWaterfallHook
凡有非同步,必有回撥
同步鉤子是通過 tap
來監聽函式的, call
來發布的。
非同步鉤子是通過 tapAsync
或 tapPromise
來監聽函式,通過 callAsync
或 promise
來發布訂閱的。
AsyncParallelHook
非同步並行, 監聽的函式會一塊執行, 哪個函式先執行完就先觸發。不需要關心監聽函式的返回值。
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
AsyncParalleBailHook
暫時不理解
AsyncSeriesHook
非同步序列鉤子類, 不關心 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
AsyncSeriesBailHook
同步序列鉤子類, 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
AsyncSeriesWaterfallHook
同步序列鉤子類, 上一個監聽函式 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
END
如果理解有誤, 麻煩糾正!