原本這篇是要說 resolve 實現的,但我發現講 resolve 以前不把 Tapable 講清楚,是搞不明白,這篇是最重要的,其餘能夠跳過,這個但願正在看的讀者,仔細看看,搞明白了,下次面試,人家問你什麼事件流,發佈訂閱,觀察者模式啊,你把這篇吹一波,確定加分很多(額,牛皮仍是要吹的)javascript
先拉遠些,咱們看三大框架,都有個生命週期的概念,在生命週期暴露些hook,生命週期說白了就是對一個流程的抽象,就像一個小河裏的水流,通過一個個大壩,規定有的大壩處理下垃圾,有的來個分流到小河裏灌溉,有的來防洪,哪一個大壩能夠捕魚,定義好每一個大壩要乾的事,那周圍的人就知道本身幹什麼事的時候,到哪一個大壩去就行了vue
當你在寫一個複雜的邏輯的,特別是暴露給不少人用的庫時,必定要先抽象出個生命週期的流程出來,會方便不少,擴展也容易不少(我在這方面就不足,常常一頓亂寫,每次升級別人依賴個人東西時候,都得手動去改代碼,靈活性不足,正在慢慢學習使用這個思惟)java
注意哈,這裏的事件
不是單純的指 dom元素的事件,指的是程序的邏輯的關係,nodejs 有個 EventEmitter ,EventEmitter 的核心函數有兩個,向事件集合中添加函數的event.on()以及用於觸發的函數event.emit()。這個本質上是個發佈訂閱,先列幾個常見的業務場景node
上面是一些具體的業務場景,讀者能夠先想一想這些你遇到了會怎麼解決webpack
咱們回到Webpack,Webpack 擴展了 EventEmitter ,把全部的邏輯抽象成各類 plugins ,要 打包 TypeScipt,加個對應的 loader,這些loader都是對應的 plugin,而後掛載上去,須要 處理編譯後的文件,自定義個 new Plugin
來掛載,上面說的幾個業務場景,Webpack 構建過程當中都有遇到,並且比上面複雜的多流程都有,那咱們理解了 Webpack 實現思想,在去處理 咱們工做中遇到的具體業務邏輯,就很是方便了,這也是網上說的,多看源碼,理解源碼,爲本身所用的緣由git
事件流最大的好處就是代碼耦合性小,把邏輯獨立到個個子模塊,可插拔web
Webpack 80%的代碼都是經過 plugins 的方式掛載的,外帶第三方 plugins,loader,同時經過 同樣掛載,我數了下 Webpack文檔,compiler暴露了 24 個 hook,compilation 暴露了 65 個 hook ,要把這麼複雜的打包流程組裝起來,webpack 就是基於事件流開發了一個獨立的骨架,這個骨架就是 Tapable,Tapable就是Webpack的大腦面試
總於說到Tapable了,下面就具體看下 Tapable 把事件流定義了那幾個類型,Tapable的講解,分爲3部分框架
先說說最簡單的 sync 實現dom
SyncHook的特色是,每一個注入的方法不關注返回值,同步的串行的執行每一個方法
看段具體代碼
const { SyncHook } = require("tapable");
class MyVue {
constructor() {
this.hooks = {
beforeCreate: new SyncHook(["beforeCreateHook"]),
created: new SyncHook(["createdHook"]),
mounted: new SyncHook(),
destroyed: new SyncHook(),
};
}
defaultBeforeCreateHook(){
this.hooks.beforeCreate.tap('1', (name) => {
console.log('default', name);
})
}
beforeCreate() {
console.log('準備初始化MyVue實例了')
//....這裏框架幹了一堆事 ,就經過 hook 把使用者注入代碼執行完成了
this.hooks.beforeCreate.call('MyVue')
}
created() {
console.log('乾點其餘事,喚起hook created ')
this.hooks.created.call(this)
}
init() {
//..... 幹一堆事
this.beforeCreate()
//..... 再幹一堆事
this.created()
}
}
const vueInstance = new MyVue()
vueInstance.hooks.beforeCreate.tap('1', (name) => {
console.log('hello', name);
})
vueInstance.hooks.beforeCreate.tap('2', (name) => {
console.log('Wellocome', name);
})
vueInstance.init()
輸出以下:
準備初始化MyVue實例了
hello MyVue
Wellocome MyVue
乾點其餘事,喚起hook created
複製代碼
這裏模仿 Vue 定義了幾個生命週期,這樣是否是就把代碼解藕了,在本身項目中就能夠經過這種方式,把外部傳入的邏輯和本身框架的邏輯剝離出來, defaultBeforeCreateHook
方法就能夠作到可插拔,在你框架邏輯變化的時候,就改變 default* 這個方法就好 咱們日常有寫 Webpack 插件的話,都是這種方式掛載上去的
SyncHook 的特色是,每一個注入的方法不關注上個方法返回值,同步的串行的執行每一個方法,下面簡單實現 SyncHook
class Hook{
constructor(args){
this.taps = []
this.interceptors = [] // 這個放在後面用
this._args = args
}
tap(name,fn){
this.taps.push({name,fn})
}
}
class SyncHook extends Hook{
call(name,fn){
try {
this.taps.forEach(tap => tap.fn(name))
fn(null,name)
} catch (error) {
fn(error)
}
}
}
複製代碼
看到 this.taps.forEach(tap => tap.fn(name))
,這裏不關注上個方法返回值
SyncWaterfallHook 和 SyncHook 的差異是 SyncWaterfallHook 注入的方法,上一個方法的返回值會作爲下個方法的參數傳入,這個和咱們最上面說的那個業務場景
複雜些的審批流程,下一個組件的狀態依賴上一個組件的返回值數據
是否是就很合適的套上了,SyncWaterfallHook 實現以下
class SyncWaterfallHook extends Hook{
call(name,fn){
try {
let result = this.taps.reduce((result,tap) => tap.fn(name),null)
fn(null,result)
} catch (error) {
fn(error)
}
}
}
複製代碼
假設有一方法返回非 undefined
,跳過剩下的 tap,當即執行回調,實現以下
class SyncBailHook extends Hook{
call(name,fn){
try {
let result = this.taps.reduce((result,tap) => {
if(result != undefined){
fn(null,result)
}else{
return tap.fn(name)
}
},null)
fn(null,result)
} catch (error) {
fn(error)
}
}
}
複製代碼
這個Hook比較適合在 知足某些條件的狀況下,執行某個邏輯,而這些條件是當下不能肯定的,須要使用者來定義,傳入框架的,說的有點繞,我不知道怎麼去描述這個場景,能夠簡單的想成,多條件判斷,只要一個返回 true,就執行具體邏輯
若是當前方法返回 非undefined
,一直執行這個方法,只有當返回 undefined
跳出循環,執行下一個方法,這個我目前沒有想到使用場景,實現方式以下
class SyncLoopHook extends Hook {
call(name, fn) {
try {
this.taps.forEach(tap => {
let result;
do {
result = tap.fn(name)
} while (result !== undefined);
})
fn(null)
} catch (error) {
fn(error)
}
}
}
複製代碼
同步 hook 寫完,理解學會這幾種用法,在日常工做中能夠大大的減小代碼耦合,值得深刻理解學習 下篇咱們講下異步Hook