版本:2.2.1
省略了context,interceptors的代码。
基于发布/订阅模式
tap
: hook上注册函数call
: 执行注册函数
升级版发布订阅(功能更多,例如:参数列表,拦截器,多种同步/异步钩子,多种函数执行流程)。
const {SyncHook} = require('tapable')
const h1 = new SyncHook(["test"]);
h1.tap("C", (test)=>console.log(test));
h1.call("1");// 打印 1
SyncHook源码
Tapable
中的各类Hook
,架构是一致的,我从SyncHook
源码举例说明。
// 简化代码,边界处理的代码已删除
function SyncHook(args = [], name = undefined) {
// 各类Hook的核心 Hook类
// 负责:tap(注册) call(调用)
const hook = new Hook(args, name);
// compile由HookCodeFactory类实现
// 负责:编译(动态生成hook.call函数)
hook.compile = COMPILE;
return hook;
}
// 通过子类实现多态
// 提高hooks的灵活性,可替换性
class SyncHookCodeFactory extends HookCodeFactory {
// 生成call函数
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
const factory = new SyncHookCodeFactory();
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
先分析Hook类
Hook类
constructo
class Hook {
constructor(args = [], name = undefined) {
// 回调中可传参数
// eg: new SyncHook(["test", "arg2", "arg3"]);
// 在执行回调函数中可进行传参
this._args = args;
this.name = name;
// tap注册函数
this.taps = [];
// 三种回调类型执行 call
this.call = CALL_DELEGATE;
this.callAsync = CALL_ASYNC_DELEGATE;
this.promise = PROMISE_DELEGATE;
// 三种注册方式
this.tap = this.tap;
this.tapAsync = this.tapAsync;
this.tapPromise = this.tapPromise;
this._x = undefined;
}
}
tab方法
tap
方法,是_tap
的包装,用于区分回调类型
// options一般是 string
// 就是tap(name,callback)
class Hook{
tap(options, fn) {
this._tap("sync", options, fn);
}
tapAsync(options, fn) {
this._tap("async", options, fn);
}
tapPromise(options, fn) {
this._tap("promise", options, fn);
}
}
_tap
将 tap
插入 this.taps
。
class Hook {
_tap(type, options, fn) {
if (typeof options === "string") {
options = { name: options.trim()};
}
options = Object.assign({ type, fn }, options);
// 拦截器执行register
options = this._runRegisterInterceptors(options);
// 将 监听options 插入
// 可以简单认为 this.taps.push[options]
this._insert(options);
// options 值: {type: "sync", fn: Function, name: "A"}
}
}
tap
流程结束。
call方法
创建call
, 执行 call(...args)
// 在Hook的constructor中 call被赋值
// 3种call调用方式
class Hook{
constructor(args = [], name = undefined) {
this.call = CALL_DELEGATE;
this.callAsync = CALL_ASYNC_DELEGATE;
this.promise = PROMISE_DELEGATE;
}
}
const CALL_DELEGATE = function(...args) {
this.call = this._createCall("sync");
return this.call(...args);
};
const CALL_ASYNC_DELEGATE = function(...args) {
this.callAsync = this._createCall("async");
return this.callAsync(...args);
};
const PROMISE_DELEGATE = function(...args) {
this.promise = this._createCall("promise");
return this.promise(...args);
};
3种调用都是_createCall
的包装。
_createCall
调用 this.compile
,必须有子类重写这个方法。
class Hook{
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
compile(options) {
throw new Error("Abstract: should be overridden");
}
}
compile
以SyncHook
的举例
// 简化 按思考顺序排列代码
function SyncHook(args = [], name = undefined) {
const hook = new Hook(args, name);
// 重写compile
hook.compile = COMPILE;
return hook;
}
// 先知道会动态返回 call函数(功能:执行tap注册函数)即可。 下面细讲
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
const factory = new SyncHookCodeFactory();
// compile都是由HookCodeFactory的子类完成
class SyncHookCodeFactory extends HookCodeFactory {
// 处理compile的call函数内容,按照什么逻辑构建.
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
Hook.call
流程图
下面我们来研究HookCodeFactory
的源码,如何动态生成call
。
HookCodeFactory类
看名字就知道,这个生成code的。
功能:遍历taps
,根据回调类型,执行逻辑,生成不同的call
函数。(拼接字符串 new Function
)
从SyncHook的compile入手
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
从setup出发。
setup
将tap
注册的函数全部取出。
class HookCodeFactory {
setup(instance, options) {
// taps中的函数保存到 _x 中。
instance._x = options.taps.map(t => t.fn);
}
}
create
动态构建call
函数。
我们只关注 type:'sync'
的。
class HookCodeFactory {
// 初始化
init(options) {
this.options = options;
// 获取args eg: new SyncHook(["test", "arg2", "arg3"]);
// args 就是 ["test", "arg2", "arg3"]
this._args = options.args.slice();
}
// 创建
create(options) {
this.init(options);
let fn;
// 区分回调类型
// 通过拼接字符串 构建 call函数
// 在下面详细讲这个拼接
switch (this.options.type) {
case "sync":
fn = new Function(
this.args(),
'"use strict";\n' +
this.header() +
this.contentWithInterceptors({
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
break;
case "async":
fn = new Function(
this.args({
after: "_callback"
}),
'"use strict";\n' +
this.header() +
this.contentWithInterceptors({
onError: err => `_callback(${err});\n`,
onResult: result => `_callback(null, ${result});\n`,
onDone: () => "_callback();\n"
})
);
break;
case "promise":
let errorHelperUsed = false;
const content = this.contentWithInterceptors({
onError: err => {
errorHelperUsed = true;
return `_error(${err});\n`;
},
onResult: result => `_resolve(${result});\n`,
onDone: () => "_resolve();\n"
});
let code = "";
code += '"use strict";\n';
code += this.header();
code += "return new Promise((function(_resolve, _reject) {\n";
if (errorHelperUsed) {
code += "var _sync = true;\n";
code += "function _error(_err) {\n";
code += "if(_sync)\n";
code +=
"_resolve(Promise.resolve().then((function() { throw _err; })));\n";
code += "else\n";
code += "_reject(_err);\n";
code += "};\n";
}
code += content;
if (errorHelperUsed) {
code += "_sync = false;\n";
}
code += "}));\n";
fn = new Function(this.args(), code);
break;
}
this.deinit();
return fn;
}
}
拼接字符串 生成 call
type == 'sync'
的部分详细讲一下。
// 全部示例都为 new SyncHook(["test", "arg2", "arg3"])
// 忽略context interceptors .
// (step 0)
fn = new Function(
this.args(), // 'test, arg2, arg3'
'"use strict";\n' +
this.header() + // var _x = this._x;
this.contentWithInterceptors({ // var _fn0 = _x[0];_fn0(test, arg2, arg3);
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
// fn最终函数为如下:
// function anonymous(test, arg2, arg3) {
// "use strict";
// var _context;
// var _x = this._x;
// var _fn0 = _x[0];
// _fn0(test, arg2, arg3);
// }
// 简化后代码
class HookCodeFactory {
// (step 1)
// 将args转换为 args 字符串
// eg: new SyncHook(["test", "arg2", "arg3"]);输出 'test, arg2, arg3'
args(){
let allArgs = this._args;
return allArgs.length === 0 ? "":allArgs.join(", ");
}
// (step 2)
// 代码顶部变量声明
// 主要将 _x(taps所有注册函数)放入函数。
header() {
let code = "";
// 省略context代码
code += "var _context;\n";
// 将 _x 放入字符串
code += "var _x = this._x;\n";
// 省略interceptors代码
return code;
}
// (step 3) 下一步在最下面的子类中
// 添加拦截器(本文不讨论)在执行content
contentWithInterceptors(options) {
// 省略interceptors代码
// 注意:content必须有子类重写
// 在最下面有重写的方法
return this.content(options);
}
// (step 3.2)
// 将sync的taps遍历合成为 执行tap注册函数的,JS代码字符串。
callTapsSeries({
onError,
onResult,
resultReturns,
onDone,
doneReturns,
rethrowIfPossible
}) {
if (this.options.taps.length === 0) return onDone();
let code = "";
let current = onDone;
for (let j = this.options.taps.length - 1; j >= 0; j--) {
const i = j;
const done = current;
// (step 3.3)
// this.callTap:将单个tap构建成 JS代码字符串
const content = this.callTap(i, {
onError: error => onError(i, error, done),
onResult:result => onResult(i, result, done),
onDone: !onResult && done,
});
current = () => content;
}
code += current();
// 举例 code: var _fn0 = _x[0];_fn0(test, arg2, arg3);
return code;
// 返回 step( 0 )
}
// (step 3.3)
// 将tap注册函数 转换为js代码字符串,
callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
let code = "";
// 省略interceptors的代码
// 将数组展开,举例 code: var _fn0 = _x[0]
code += `var _fn${tapIndex} = _x[${tapIndex}];\n`;
const tap = this.options.taps[tapIndex];
switch (tap.type) {
case "sync":
code += `_fn${tapIndex}(${this.args()});\n`;
// 举例 code: var _fn0 = _x[0];_fn0(test, arg2, arg3);
if (onDone) {
code += onDone(); // onDone将所有tap字符串连接起来.
}
break;
case "async":
// 略过,async类的代码 逻辑也是生成字符串
break;
case "promise":
// 略过,promise类的代码 逻辑也是生成字符串
break;
}
// 举例 code: var _fn0 = _x[0];_fn0(test, arg2, arg3);
return code;
}
}
// 子类重写的content方法
class SyncHookCodeFactory extends HookCodeFactory {
// (step 3.1)
// content方法
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
HookCodeFactory流程图
Tapable架构图
总结
Hook
:负责订阅发布。tap
(注册函数),call
(执行)。
HookCodeFactory
:负责编译,动态生成 供Hook
使用的call
函数。- 区分回调类型:
sync
、async
、promise
。 内部compile
函数自行区分。 - 区分执行流程:继承的子类,**重写 **
content
,分流程调用- 串行
sync
:callTapsSeries
- 循环
loop
:callTapsLooping
- 并行
parallel
:callTapsParallel
- 串行
- 区分回调类型:
(各类Hook):内聚上述逻辑,实现不同的执行逻辑和回调类型。
如何实现灵活的订阅发布?
- 一个类实现 订阅发布。
- 一个类实现 灵活。
- 然后将2个类进行组合,完善具体逻辑。