記錄下本身在前端路上爬坑的經歷 加深印象,正文開始~前端
tapable是webpack的核心依賴庫 想要讀懂webpack源碼 就必須首先熟悉tapable
ok.下面是webapck中引入的tapable鉤子 因而可知 在webpack中tapable的重要性node
const { SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook, AsyncParallelHook, AsyncParallelBailHook, AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook } = require("tapable");
這些鉤子可分爲同步的鉤子和異步的鉤子,Sync開頭的都是同步的鉤子,Async開頭的都是異步的鉤子。而異步的鉤子又可分爲並行和串行,其實同步的鉤子也能夠理解爲串行的鉤子。react
本文將根據如下章節分別梳理每一個鉤子webpack
同步鉤子
首先安裝tapable
npm i tapable -Dgit
SyncHook
SyncHook是簡單的同步鉤子,它很相似於發佈訂閱。首先訂閱事件,觸發時按照順序依次執行,因此說同步的鉤子都是串行的。github
const { SyncHook } = require('tapable'); class Hook{ constructor(){ /** 1 生成SyncHook實例 */ this.hooks = new SyncHook(['name']); } tap(){ /** 2 註冊監聽函數 */ this.hooks.tap('node',function(name){ console.log('node',name); }); this.hooks.tap('react',function(name){ console.log('react',name); }); } start(){ /** 3出發監聽函數 */ this.hooks.call('call end.'); } } let h = new Hook(); h.tap();/** 相似訂閱 */ h.start();/** 相似發佈 */ /* 打印順序: node call end. react call end. */
能夠看到 它是按照順序依次打印的,其實說白了就是發佈和訂閱。接下來咱們就手動實現它。web
class SyncHook{ // 定義一個SyncHook類 constructor(args){ /* args -> ['name']) */ this.tasks = []; } /** tap接收兩個參數 name和fn */ tap(name,fn){ /** 訂閱:將fn放入到this.tasks中 */ this.tasks.push(fn); } start(...args){/** 接受參數 */ /** 發佈:將this.taks中的fn依次執行 */ this.tasks.forEach((task)=>{ task(...args); }); } } let h = new SyncHook(['name']); /** 訂閱 */ h.tap('react',(name)=>{ console.log('react',name); }); h.tap('node',(name)=>{ console.log('node',name); }); /** 發佈 */ h.start('end.'); /* 打印順序: react end. node end. */
SyncBailHook
SyncBailHook 從字面意思上理解爲帶有保險的同步的鉤子,帶有保險意思是 根據每一步返回的值來決定要不要繼續往下走,若是return了一個非undefined的值 那就不會往下走,注意 若是什麼都不return 也至關於return了一個undefined。npm
const { SyncBailHook } = require('tapable'); class Hook{ constructor(){ this.hooks = new SyncBailHook(['name']); } tap(){ this.hooks.tap('node',function(name){ console.log('node',name); /** 此處return了一個非undefined * 代碼到這裏就不會繼續執行餘下的鉤子 */ return 1; }); this.hooks.tap('react',function(name){ console.log('react',name); }); } start(){ this.hooks.call('call end.'); } } let h = new Hook(); h.tap(); h.start(); /* 打印順序: node call end. */
手動實現segmentfault
class SyncHook{ constructor(args){ this.tasks = []; } tap(name,fn){ this.tasks.push(fn); } start(...args){ let index = 0; let result; /** 利用do while先執行一次的特性 */ do{ /** 拿到每一次函數的返回值 result */ result = this.tasks[index++](...args); /** 若是返回值不爲undefined或者執行完畢全部task -> 中斷循環 */ }while(result === undefined && index < this.tasks.length); } } let h = new SyncHook(['name']); h.tap('react',(name)=>{ console.log('react',name); return 1; }); h.tap('node',(name)=>{ console.log('node',name); }); h.start('end.'); /* 打印順序: react end. */
SyncWaterfallHook
SyncWaterfallHook是同步的瀑布鉤子,瀑布怎麼理解呢? 其實就是說它的每一步都依賴上一步的執行結果,也就是上一步return的值就是下一步的參數。異步
const { SyncWaterfallHook } = require('tapable'); class Hook{ constructor(){ this.hooks = new SyncWaterfallHook(['name']); } tap(){ this.hooks.tap('node',function(name){ console.log('node',name); /** 此處返回的值做爲第二步的結果 */ return '第一步返回的結果'; }); this.hooks.tap('react',function(data){ /** 此處data就是上一步return的值 */ console.log('react',data); }); } start(){ this.hooks.call('callend.'); } } let h = new Hook(); h.tap(); h.start(); /* 打印順序: node callend. react 第一步返回的結果 */
手動實現:
class SyncWaterFallHook{ constructor(args){ this.tasks = []; } tap(name,fn){ this.tasks.push(fn); } start(...args){ /** 解構 拿到tasks中的第一個task -> first */ let [first, ...others] = this.tasks; /** 利用reduce() 累計執行 * 首先傳入第一個 first 並執行 * l是上一個task n是當前task * 這樣知足了 下一個函數依賴上一個函數的執行結果 */ others.reduce((l,n)=>{ return n(l); },first(...args)); } } let h = new SyncWaterFallHook(['name']); /** 訂閱 */ h.tap('react',(name)=>{ console.log('react',name); return '我是第一步返回的值'; }); h.tap('node',(name)=>{ console.log('node',name); }); /** 發佈 */ h.start('end.'); /* 打印順序: react end. node 我是第一步返回的值 */
SyncLoopHook
SyncLoopHook是同步的循環鉤子。 循環鉤子很好理解,就是在知足必定條件時 循環執行某個函數:
const { SyncLoopHook } = require('tapable'); class Hook{ constructor(){ /** 定義一個index */ this.index = 0; this.hooks = new SyncLoopHook(['name']); } tap(){ /** 箭頭函數 綁定this */ this.hooks.tap('node',(name) => { console.log('node',name); /** 當不知足條件時 會循環執行該函數 * 返回值爲udefined時 終止該循環執行 */ return ++this.index === 5?undefined:'學完5遍node後再學react'; }); this.hooks.tap('react',(data) => { console.log('react',data); }); } start(){ this.hooks.call('callend.'); } } let h = new Hook(); h.tap(); h.start(); /* 打印順序: node callend. node callend. node callend. node callend. node callend. react callend. */
能夠看到 執行了5遍node callend後再繼續往下執行。也就是當返回undefined時 纔會繼續往下執行:
class SyncWaterFallHook{ constructor(args){ this.tasks = []; } tap(name,fn){ this.tasks.push(fn); } start(...args){ let result; this.tasks.forEach((task)=>{ /** 注意 此處do{}while()循環的是每一個單獨的task */ do{ /** 拿到每一個task執行後返回的結果 */ result = task(...args); /** 返回結果不是udefined時 會繼續循環執行該task */ }while(result !== undefined); }); } } let h = new SyncWaterFallHook(['name']); let total = 0; /** 訂閱 */ h.tap('react',(name)=>{ console.log('react',name); return ++total === 3?undefined:'繼續執行'; }); h.tap('node',(name)=>{ console.log('node',name); }); /** 發佈 */ h.start('end.'); /* 打印順序: react end. react end. react end. node end. */
能夠看到, 執行了3次react end.後 才繼續執行了下一個task -> node end。至此,咱們把tapable的全部同步鉤子都解析完畢. 異步鉤子比同步鉤子麻煩些,咱們會在下一章節開始解析異步的鉤子.