爲何咱們要學tapable,由於....webpack源碼裏面都是用的tapable來實現鉤子掛載的,做爲一個有點追求的code,webpack怎麼能只知足於用呢?固然是要去看源碼,寫loader,plugin啦.在這以前,要是不清楚tapable的用法,源碼那是更不用看了,看不懂.....因此,今天來說一下tapable吧webpack
webpack本質上是一種事件流的機制,他的工做流程就是將各個插件串聯起來,而實現這一切的核心就是Tapable,webpack中最核心的負責編譯的Compiler和負責建立的bundles的Compilation都是Tapable的實例web
tapable建立實例時傳遞的參數對於程序運行並無任何做用,只是給源碼閱讀者提供幫助redux
一樣的,在使用tap*註冊監聽時,傳遞的第一個參數,也只是一個標識,並不會在程序運行中產生任何影響。而第二個參數則是回調函數promise
const {
SyncHook,
SyncBailHook,
SyncWaterHook,
SyncLoopHook
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
複製代碼
序號 | 鉤子名稱 | 執行方式 | 使用要點 |
---|---|---|---|
1 | SyncHook | 同步串行 | 不關心監聽函數的返回值 |
2 | SyncBailHook | 同步串行 | 只要監聽函數中有一個函數的返回值不爲null,則跳過剩餘邏輯 |
3 | SyncWaterfallHook | 同步串行 | 上一個監聽函數的返回值將做爲參數傳遞給下一個監聽函數 |
4 | SyncLoopHook | 同步串行 | 當監聽函數被觸發的時候,若是該監聽函數返回true時則這個監聽函數會反覆執行,若是返回 undefined 則表示退出循環 |
5 | AsyncParallelHook | 異步並行 | 不關心監聽函數的返回值 |
6 | AsyncParallelBailHook | 異步並行 | 只要監聽函數的返回值不爲 null,就會忽略後面的監聽函數執行,直接跳躍到callAsync等觸發函數綁定的回調函數,而後執行這個被綁定的回調函數 |
7 | AsyncSeriesHook | 異步串行 | 不關心callback()的參數 |
8 | AsyncSeriesBailHook | 異步串行 | callback()的參數不爲null,就會直接執行callAsync等觸發函數綁定的回調函數 |
9 | AsyncSeriesWaterfallHook | 異步串行 | 上一個監聽函數的中的callback(err, data)的第二個參數,能夠做爲下一個監聽函數的參數 |
串行同步執行,不關心返回值 在SyncHook的實例上註冊了tap以後,只要實例調用了call方法,那麼這些tap的回掉函數必定會順序執行一遍異步
let queue = new SyncHook(['沒任何做用的參數']);
queue.tap(1,(name,age)=>{
console.log(name,age)
})
queue.tap(2,(name,age)=>{
console.log(name,age)
})
queue.tap(3,(name,age)=>{
console.log(name,age)
})
queue.call('bearbao',8)
// 輸出結果
// 'bearbao' 8
// 'bearbao' 8
// 'bearbao' 8
複製代碼
class SyncHook {
constructor(){
this.listeners = [];
}
tap(formal,listener){
this.listeners.push(listener)
}
call(...args){
this.listeners.forEach(l=>l(...args))
}
}
複製代碼
串行同步執行,有一個返回值不爲null則跳過剩下的邏輯函數
let queue = new SyncBailHook(['name'])
queue.tap(1,name=>{
console.log(name)
})
queue.tap(1,name=>{
console.log(name)
return '1'
})
queue.tap(1,name=>{
console.log(name)
})
queue.call('bearbao')
// 輸出結果,只執行前面兩個回調,第三個不執行
// bearbao
// bearbao
複製代碼
實現oop
class SyncBailHook {
constructor(){
this.listeners = [];
}
tap(formal,listener){
this.listeners.push(listener)
}
call(...args){
for(let i=0;i<this.listeners.length;i++){
if(this.listeners[i]()) break;
}
}
}
複製代碼
串行同步執行,第一個註冊的回調函數會接收call傳進來的全部參數,以後的每一個回調函數只接收到一個參數,就是上一個回調函數的返回值.post
let queue = new SyncWaterHook(['name','age']);
queue.tap(1,(name,age)=>{
console.log(name,age)
return 1
})
queue.tap(2,(ret)=>{
console.log(ret)
return 2
})
queue.tap(3,(ret)=>{
console.log(ret)
return 3
})
queue.call('bearbao', 3)
// 輸出結果
// bearbao 3
// 1
// 2
複製代碼
SyncWaterHook 實現. SyncWaterHook這個方法很像redux中的compose方法,都是將一個函數的返回值做爲參數傳遞給下一個函數.ui
對下面實現的call方法若是有疑惑,看不大懂的同窗能夠移步我以前對於compose函數的解讀,裏面有詳細的介紹,這裏就很少加贅述了this
class SyncWaterHook{
constructor(){
this.listeners = [];
}
tap(formal,listener){
this.listener.unshift(listener);
}
call(...args){
this.listeners.reduce((a,b)=>(...args)=>a(b(...args)))(...args)
}
}
複製代碼
串行同步執行, 監聽函數返回true表示繼續循環,返回undefined表示循環結束
let queue = new SyncLoopHook;
let index = 0;
queue.tap(1,_=>{
index++
if(index<3){
console.log(index);
return true
}
})
queue.call();
// 輸出結果
// 1
// 2
複製代碼
SyncLoopHook實現
class SyncLoopHook{
constructor() {
this.tasks=[];
}
tap(name,task) {
this.tasks.push(task);
}
call(...args) {
this.tasks.forEach(task => {
let ret=true;
do {
ret = task(...args);
}while(ret)
});
}
}
複製代碼
異步並行執行
不關心監聽函數的返回值.
有三種註冊/發佈的模式,以下
異步訂閱 | 調用方法 |
---|---|
tap | callAsync |
tapAsync | callAsync |
tapPromise | promise |
觸發函數的參數,出了最後一個參數是異步監聽回調函數執行完成以後的回調,其餘的參數都是傳遞給回調函數的參數
let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tap('1',function(name){
console.log(name,1);
});
queue.tap('2',function(name){
console.log(name,2);
});
queue.tap('3',function(name){
console.log(name,3);
});
queue.callAsync('bearbao',err=>{
console.log(err);
console.timeEnd('cost');
});
// 執行結果
/* bearbao 1 bearbao 2 bearbao 3 cost: 4.720ms */
複製代碼
實現
class AsyncParallelHook {
constructor(){
this.listeners = [];
}
tap(name,listener){
this.listeners.push(listener);
}
callAsync(){
this.listeners.forEach(listener=>listener(...arguments));
Array.from(arguments).pop()();
}
}
複製代碼
注意,這裏有個特殊的地方,如何確認某個回調執行完了呢?,每一個監聽回調的最後一個參數是一個回調函數,當執行callback以後,會認爲當前函數執行完畢
let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tapAsync('1',function(name,callback){
setTimeout(function(){
console.log(name, 1);
callback();
},1000)
});
queue.tapAsync('2',function(name,callback){
setTimeout(function(){
console.log(name, 2);
callback();
},2000)
});
queue.tapAsync('3',function(name,callback){
setTimeout(function(){
console.log(name, 3);
callback();
},3000)
});
queue.callAsync('bearbao',err=>{
console.log(err);
console.timeEnd('cost');
});
// 輸出結果
/* bearbao 1 bearbao 2 bearbao 3 cost: 3000.448974609375ms */
複製代碼
實現
class AsyncParallelHook {
constructor(){
this.listeners = [];
}
tapAsync(name,listener){
this.listeners.push(listener);
}
callAsync(...arg){
let callback = arg.pop();
let i = 0;
let done = ()=>{
if(++i==this.listeners.length){
callback()
}
}
this.listeners.forEach(listener=>listener(...arg,done));
}
}
複製代碼
使用tapPromise註冊監聽時,每一個回調函數的返回值必須是一個Promise的實例
let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tapPromise('1',function(name){
return new Promise(function(resolve,reject){
setTimeout(function(){
console.log(1);
resolve();
},1000)
});
});
queue.tapPromise('2',function(name){
return new Promise(function(resolve,reject){
setTimeout(function(){
console.log(2);
resolve();
},2000)
});
});
queue.tapPromise('3',function(name){
return new Promise(function(resolve,reject){
setTimeout(function(){
console.log(3);
resolve();
},3000)
});
});
queue.promise('bearbao').then(()=>{
console.timeEnd('cost');
})
// 執行記過
/* 1 2 3 cost: 3000.448974609375ms */
複製代碼
實現
class AsyncParallelHook {
constructor(){
this.listeners = [];
}
tapPromise(name,listener){
this.listeners.push(listener);
}
promise(...arg){
let i = 0;
return Promise.all(this.listeners.map(l=>l(arg)))
}
}
複製代碼
一不當心又到1點了,爲了可以得到長壽成就,今天就先寫到這裏吧,後續幾個方法,過兩天再更新上來
若是以爲還能夠,能在諸君的編碼之路上帶來一點幫助,請點贊鼓勵一下,謝謝!