tapable学习
基本使用
Tapable是一个类似于 Node.js 中的 EventEmitter 的库,但它更专注于自定义事件的触发和处理。
const {SyncHook} = require("tapable"); //同步钩子
//第一步:实例化钩子函数,定义形参
const syncHook = new SyncHook(["name", "age"]);
//第二步:注册事件1
syncHook.tap("监听器1", (name, age) => {
console.log("监听器1:", name, age);
});
//第二步:注册事件2
syncHook.tap("监听器2", (name) => {
console.log("监听器2", name);
});
//第三步:注册事件3
syncHook.tap("监听器3", (name) => {
console.log("监听器3", name);
});
//第三步:触发事件,这里传的是实参,会被每一个注册函数接收到
syncHook.call("ll", "20");
// 监听器1: ll 20
// 监听器2 ll
// 监听器3 ll
采用的是发布订阅模式,通过 tap 函数注册监听函数,然后通过 call 函数按顺序执行之前注册的函数。
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");hooks
同步钩子:tap 方法是唯一注册事件的方法,通过 call 方法触发同步钩子的执行。 异步钩子:可以通过 tap、tapAsync、tapPromise三种方式来注册,通过对应的 callAsync、promise 这两种方式来触发注册的函数。 异步钩子分为两类:
- 异步串行钩子( AsyncSeries ):可以被串联(连续按照顺序调用)执行的异步钩子函数。
- 异步并行钩子( AsyncParallel ):可以被并联(并发调用)执行的异步钩子函数。
hooks执行机制
- Basic Hook : 基本类型的钩子,执行每一个注册的事件函数,并不关心每个被调用的事件函数返回值如何。
- Waterfall : 瀑布类型的钩子,如果前一个事件函数的结果 result !== undefined,则 result 会作为后一个事件函数的第一个参数。
- Bail : 保险类型钩子,执行每一个事件函数,遇到第一个结果 result !== undefined 则返回,不再继续执行。
- Loop : 循环类型钩子,不停的循环执行事件函数,直到所有函数结果 result === undefined。
WaterfallHook 示例
const { SyncWaterfallHook } = require("tapable");
const hook = new SyncWaterfallHook(["author", "age"]);
//通过tap函数注册事件
hook.tap("测试1", (param1, param2) => {
console.log("测试1接收的参数:", param1, param2);
});
hook.tap("测试2", (param1, param2) => {
console.log("测试2接收的参数:", param1, param2);
return "123";
});
hook.tap("测试3", (param1, param2) => {
console.log("测试3接收的参数:", param1, param2);
});
//通过call方法触发事件
hook.call("ll", "20");
// 测试1接收的参数: ll 20
// 测试2接收的参数: ll 20
// 测试3接收的参数: 123 20
异步hook
const { AsyncParallelHook } = require("tapable");
const hook = new AsyncParallelHook(["author", "age"]);
console.time("time");
// 异步钩子需要通过tapAsync函数注册事件,同时也会多一个callback参数,
// 执行callback告诉hook该注册事件已经执行完成
hook.tapAsync("测试1", (param1, param2, callback) => {
setTimeout(() => {
console.log("测试1接收的参数:", param1, param2);
callback();
}, 2000);
});
hook.tapAsync("测试2", (param1, param2, callback) => {
console.log("测试2接收的参数:", param1, param2);
callback();
});
hook.tapAsync("测试3", (param1, param2, callback) => {
console.log("测试3接收的参数:", param1, param2);
callback();
});
//call方法只有同步钩子才有,异步钩子得使用callAsync
hook.callAsync("ll", "20", (err, result) => {
//等全部都完成了才会走到这里来
console.log("这是成功后的回调", err, result);
console.timeEnd("time");
});
// 测试2接收的参数: ll 20
// 测试3接收的参数: ll 20
// 测试1接收的参数: ll 20
// 这是成功后的回调 undefined undefined
// time: 2.005s
核心思想
参照 SyncHook 钩子的使用
- 实例化 hook
- 通过 tap 函数添加订阅
- 调用 call 函数触发事件
tap:将监听信息组装存放在 this.taps 中
this.taps = [
{
name: "监听器1",
type: "sync",
fn: (param1, param2) => {
console.log("监听器1接收参数:", name, age);
},
},
]; //用来存放监听
/*
* tap
*/
this.tap(option, fn) {
//如果传入的是字符串,包装成对象
if (typeof option === "string") {
option = { name: option };
}
// type 确定执行类型
const tapInfo = { ...option, type: "sync", fn };
this.taps.push(tapInfo);
}
call:按照指定的类型去执行 this.tabs 中订阅信息的 fn,先动态生成执行代码,再执行生成的代码
源码中通过 compile 创建执行函数
const CALL_DELEGATE = function(...args) {
// 获取执行函数
this.call = this._createCall("sync");
return this.call(...args); // 执行
};
this.call = CALL_DELEGATE;
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
// 通过 this.compile 生成执行函数compile 简单实现
class SyncHook {
//省略其他
compile({ args, taps, type }) {
const getHeader = () => {
let code = "";
code += `var taps=this.taps;\n`;
return code;
};
const getContent = () => {
let code = "";
for (let i = 0; i < taps.length; i++) {
code += `var fn${i}=taps[${i}].fn;\n`;
code += `fn${i}(${args.join(",")});\n`;
}
return code;
};
// 利用 Function 创建返回执行函数
return new Function(args.join(","), getHeader() + getContent());
}
}
compile 中通过 new Function 创建执行函数,也就是每次执行call的时候 才会生成执行函数,为了性能考虑。相较于 forEach 会更快一些。
参考
Is the new Function performance really good? · Issue #162 · webpack/tapable