Appearance
tapable 事件流执行顺序
tapable 事件流的执行顺序是通过 注册时间、before、stage 三个来决定的
我们先看一个例子
js
const { SyncHook } = require('tapable');
// 创建一个同步事件流实例对象
const h1 = new SyncHook(['xxx', 'arg2']);
h1.intercept({
call: (source, target, routesList) => {
console.log('Starting to h1 routes');
},
register: tapInfo => {
// tapInfo = { type: "promise", name: "GoogleMapsPlugin", fn: ... }
console.log(`${tapInfo.name} is doing its job`);
return tapInfo; // may return a new tapInfo object
},
tap: tap => {},
});
// 添加同步事件A
h1.tap('A', function(args) {
console.log('A', args);
return 'b';
});
// 添加同步事件B
h1.tap('B', function() {
console.log('b');
});
// 添加同步事件C
h1.tap('C', function() {
console.log('c');
});
// 添加同步事件F
h1.tap(
{
name: 'F',
before: 'D',
},
function() {
console.log('F');
}
);
// 添加同步事件E
h1.tap(
{
name: 'E',
before: 'C',
},
function() {
console.log('E');
}
);
// 添加同步事件D
h1.tap('D', function() {
console.log('D');
});
// 添加同步事件D
h1.tap(
{
name: 'G',
stage: 10,
},
function() {
console.log('G');
}
);
// 添加同步事件D
h1.tap(
{
name: 'H',
stage: 12,
},
function() {
console.log('H');
}
);
// 执行这个事件流
h1.call(7777);
然后我们看结果
js
A is doing its job
B is doing its job
C is doing its job
F is doing its job
E is doing its job
D is doing its job
G is doing its job
H is doing its job
Starting to h1 routes
F
A 7777
b
E
c
D
G
H
我们代码写入的顺序是 ABCFEDGH 但是执行的顺序却是:FABECDGH。具体为什么可以看 tapable 的 Hook.js 的 _insert()方法,然后通过 option.before 去循环遍历已有的事件流,如果 if(before.has(x.name)) 就删除一个,直到 before.size === 0,然后通过 option.stage 去判断前面的事件流 是否 if(xStage > stage),如果更大,那就继续向前。 可见事件流的顺序是按照 先判断是否 before 之前,然后再根据 option.stage 的大小,大的在前
js
class Hook {
constructor(args) {
if (!Array.isArray(args)) args = [];
this._args = args;
this.taps = [];
this.interceptors = [];
// 初始化的时候 通过函数柯里化 去生成 xx.call() 处理函数
this.call = this._call = this._createCompileDelegate('call', 'sync');
this.promise = this._promise = this._createCompileDelegate('promise', 'promise');
this.callAsync = this._callAsync = this._createCompileDelegate('callAsync', 'async');
this._x = undefined;
}
compile(options) {}
_createCall(type) {}
_createCompileDelegate(name, type) {}
/**
* 添加事件流
*
*
1. 添加同步事件B option为字符串 直接作为事件的名称
h1.tap('B', function () {
console.log('b');
});
2. 添加同步事件E option为对象 name作为事件的名称
h1.tap(
{
name: 'E',
before: 'C',
},
function () {}
);
*
*
*/
tap(options, fn) {
// 处理第一个参数 支持字符串和对象类型 ,字符串直接作为事件名称;对象获取其中的name作为事件的名称 fn作为回调函数
if (typeof options === 'string') options = { name: options };
if (typeof options !== 'object' || options === null)
throw new Error('Invalid arguments to tap(options: Object, fn: function)');
options = Object.assign({ type: 'sync', fn: fn }, options);
// 事件的名称不能为空
if (typeof options.name !== 'string' || options.name === '') throw new Error('Missing name for tap');
options = this._runRegisterInterceptors(options);
// 添加事件流
this._insert(options);
}
tapAsync(options, fn) {
if (typeof options === 'string') options = { name: options };
if (typeof options !== 'object' || options === null)
throw new Error('Invalid arguments to tapAsync(options: Object, fn: function)');
options = Object.assign({ type: 'async', fn: fn }, options);
if (typeof options.name !== 'string' || options.name === '') throw new Error('Missing name for tapAsync');
options = this._runRegisterInterceptors(options);
this._insert(options);
}
tapPromise(options, fn) {
if (typeof options === 'string') options = { name: options };
if (typeof options !== 'object' || options === null)
throw new Error('Invalid arguments to tapPromise(options: Object, fn: function)');
options = Object.assign({ type: 'promise', fn: fn }, options);
if (typeof options.name !== 'string' || options.name === '') throw new Error('Missing name for tapPromise');
options = this._runRegisterInterceptors(options);
this._insert(options);
}
_runRegisterInterceptors(options) {
for (const interceptor of this.interceptors) {
if (interceptor.register) {
const newOptions = interceptor.register(options);
if (newOptions !== undefined) options = newOptions;
}
}
return options;
}
withOptions(options) {}
isUsed() {}
intercept(interceptor) {}
_resetCompilation() {}
/**
* 添加一个事件流
*
* 此函数主要是通过 option.before 和 option.stage 来决定当前添加的事件流在队列中的位置
*
*
*/
_insert(item) {
this._resetCompilation();
// 处理before 支持 option.before = 'D' 也支持多个 option.before = ['D' ,'F' ,'D' ]
let before;
if (typeof item.before === 'string') before = new Set([item.before]);
// 当然也支持数组的方式,去决定在多个事件之前
else if (Array.isArray(item.before)) {
before = new Set(item.before);
}
// 除了 before,option.stage权重 也决定事件流的位置
let stage = 0;
if (typeof item.stage === 'number') stage = item.stage;
// 通过遍历原来的 this.taps中的数据,然后通过 before.has(x.name) 和 if(xStage > stage) 来不断的前移插入的下标
let i = this.taps.length;
while (i > 0) {
i--;
const x = this.taps[i];
this.taps[i + 1] = x;
const xStage = x.stage || 0;
if (before) {
if (before.has(x.name)) {
before.delete(x.name);
continue;
}
if (before.size > 0) {
continue;
}
}
if (xStage > stage) {
continue;
}
// 如果是最后一个,那么因为 i-- ,所以需要向后补一位
i++;
break;
}
// 将事件流保存到 对应的下标上
this.taps[i] = item;
}
}
module.exports = Hook;
从上面的代码中可以看出不管我们是通过 hook.tap()、hook.tapAsync()、hook.tapPromise()去注册对应的事件流,都是通过 hooks._insert()
去真正的添加这个事件。
E 在 C 前很好解释 因为 name e 设置了 before c
哪为什么 F 在第一个?
json
{
"name": "F",
"before": "D"
}
其实很好解释 因为其设置了 before ,但是直到最后都没有找到 D 那么就只能放在第一个了, 所以如果其 before: [ 'D' , 'A' ] 其也会放在第一个。