Webpack源碼分析(3)--之 Webpack的靈魂強大的事件流Tapable(I)--同步 hook

Webpack源碼分析(3)--之 Webpack的靈魂強大的事件流Tapable(I)--同步 hook

原本這篇是要說 resolve 實現的,但我發現講 resolve 以前不把 Tapable 講清楚,是搞不明白,這篇是最重要的,其餘能夠跳過,這個但願正在看的讀者,仔細看看,搞明白了,下次面試,人家問你什麼事件流,發佈訂閱,觀察者模式啊,你把這篇吹一波,確定加分很多(額,牛皮仍是要吹的)javascript

生命週期的概念

先拉遠些,咱們看三大框架,都有個生命週期的概念,在生命週期暴露些hook,生命週期說白了就是對一個流程的抽象,就像一個小河裏的水流,通過一個個大壩,規定有的大壩處理下垃圾,有的來個分流到小河裏灌溉,有的來防洪,哪一個大壩能夠捕魚,定義好每一個大壩要乾的事,那周圍的人就知道本身幹什麼事的時候,到哪一個大壩去就行了vue

當你在寫一個複雜的邏輯的,特別是暴露給不少人用的庫時,必定要先抽象出個生命週期的流程出來,會方便不少,擴展也容易不少(我在這方面就不足,常常一頓亂寫,每次升級別人依賴個人東西時候,都得手動去改代碼,靈活性不足,正在慢慢學習使用這個思惟)java

事件流的概念

注意哈,這裏的事件不是單純的指 dom元素的事件,指的是程序的邏輯的關係,nodejs 有個 EventEmitter ,EventEmitter 的核心函數有兩個,向事件集合中添加函數的event.on()以及用於觸發的函數event.emit()。這個本質上是個發佈訂閱,先列幾個常見的業務場景node

  • 你在寫一套通用的cli的時候,每一個bu,小組有不一樣模版要求,有的須要先建立遠程git倉庫,有的須要打包後發佈到 cdn 等定製化需求,你怎麼樣作到可插拔,而不是一坨坨 if else
  • 商品組件加入購物車後,須要觸發 頭部購物車數字狀態改變,購物車金額數字改變,優惠券組件展現,左側其餘商品組件初始化,這些觸發相互不影響的
  • step by step,新功能引導流程, 組件間狀態不依賴
  • 複雜些的審批流程,下一個組件的狀態依賴上一個組件的返回值數據
  • 你須要寫一個移動端用戶認證的服務,這個服務總共分爲2部分,資料審覈上傳,審覈經過後展現信息,可是,遊戲BU的資料審覈只須要上傳個身份證圖片,信貸 BU 的要經歷攝像頭拍照,眨眼睛,說話,小說 BU 和在信貸 BU 的基礎上還多了遊戲部門的身份證環節

上面是一些具體的業務場景,讀者能夠先想一想這些你遇到了會怎麼解決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部分框架

  • 同步事件流
  • 異步事件流
  • 組合事件流

tapable.png-774.3kB

先說說最簡單的 sync 實現dom

SyncHook

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

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)
        }
        
    }
}
複製代碼

SyncBailHook

假設有一方法返回非 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,就執行具體邏輯

SyncLoopHook

若是當前方法返回 非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

相關文章
相關標籤/搜索