为何咱们要学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点了,为了可以得到长寿成就,今天就先写到这里吧,后续几个方法,过两天再更新上来
若是以为还能够,能在诸君的编码之路上带来一点帮助,请点赞鼓励一下,谢谢!