调试过程了解SyncHook
案例运行基本原理let hook = new SyncHook(['name', 'age'])
操作构建了一个SyncHook
实例,挂载一些属性核心属性是_x和taps属性,最后调用call方法
// SyncHook.js
// 非tap模式全部抛出异常 tapAsync、 tapPromise
const TAP_ASYNC = () => {
throw new Error("tapAsync is not supported on a SyncHook");
};
const TAP_PROMISE = () => {
throw new Error("tapPromise is not supported on a SyncHook");
};
function SyncHook(args = [], name = undefined) {
const hook = new Hook(args, name);
// 挂载属性
hook.constructor = SyncHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
hook.compile = COMPILE;
return hook;
}
// Hook.js
constructor(args = [], name = undefined) {
// 挂载了许多属性
this._args = args;
this.name = name;
this.taps = [];
this.interceptors = [];
this._call = CALL_DELEGATE;
this.call = CALL_DELEGATE;
this._callAsync = CALL_ASYNC_DELEGATE;
this.callAsync = CALL_ASYNC_DELEGATE;
this._promise = PROMISE_DELEGATE;
this.promise = PROMISE_DELEGATE;
this._x = undefined;
this.compile = this.compile;
this.tap = this.tap;
this.tapAsync = this.tapAsync;
this.tapPromise = this.tapPromise;
}
_tap(type, options, fn) {
if (typeof options === "string") {
options = {
name: options.trim()
};
} else if (typeof options !== "object" || options === null) {
throw new Error("Invalid tap options");
}
if (typeof options.name !== "string" || options.name === "") {
throw new Error("Missing name for tap");
}
if (typeof options.context !== "undefined") {
deprecateContext();
}
// 浅拷贝,并且通过_insert方法赋值给taps数组
options = Object.assign({ type, fn }, options);
options = this._runRegisterInterceptors(options);
this._insert(options);
}
根据核心逻辑,手写实现,源码中一些方法有些谜之操作,可以用一些简便的方式来实现
- 实例化 hook , 定义 _x = [f1, f2, ...] taps = [{}, {}]
- 实例调用 tap taps = [{}, {}]
- 调用 call 方法, HookCodeFactory 类中setup准备数据 create拼接函数
Hook.js
class Hook {
// args默认值为空数组
constructor(args = []) {
this.args = args
this.taps = [] // 用来存储组装好的信息
this._x = undefined // 在代码工厂函数中会给_x =[f1,f2,...]
}
tap(options, fn) {
if (typeof options === "string") {
// options组装
options = {
name: options
}
}
options = Object.assign({
fn
}, options) //{fnL...,name:fn1}
// 调用以下方法将组装好的options添加至[]
this._insert(options)
}
_insert(options) {
// 源码中有些谜之操作,其实直接复制就可以
this.taps[this.taps.length] = options
}
_createCall() {
return this.compile({
taps: this.taps,
args: this.args,
})
}
call(...args) {
// 创建将来要具体执行的函数代码结构
let callFn = this._createCall()
// 调用上述函数并且传参args
return callFn.apply(this, args)
}
}
module.exports = Hook
SyncHook.js
let Hook = require('./Hook.js')
class HookCodeFactory {
// 准备后续需要使用到的数据
setup(instance, options) {
this.options = options // 源码中通过init实现,这里直接挂载this上
instance._x = options.taps.map(item => item.fn) // fn来自于Hook类挂载的fn
}
args() {
return this.options.args.join(',') // ['name','age'] => name,age
}
header() {
return `var _x = this._x;`;
}
content() {
let code = ``
// 循环options.taps
for (var i = 0; i < this.options.taps.length; i++) {
code += `var _fn${i} = _x[${i}];_fn${i}(${this.args()});`
}
return code
}
// 创建一段可执行代码体并返回
create() {
let fn
// fn = new Function('name,age', 'var _x = this._x,var _fn0=_x[0];_fn0(name,age);') 源码中会有代码拼接
fn = new Function(this.args(), this.header() + this.content())
return fn
}
}
let factory = new HookCodeFactory()
class SyncHook extends Hook {
constructor(args) {
super(args)
}
compile(options) { // optoins结构{taps:[{},{}],args:[name,age]}
factory.setup(this, options)
return factory.create(options)
}
}
module.exports = SyncHook
测试用例
const SyncHook = require('./SyncHook.js')
let hook = new SyncHook(['name', 'age'])
hook.tap('fn1', function (name, age) {
console.log('fn1', name, age)
})
hook.tap('fn2', function (name, age) {
console.log('fn2', name, age)
})
hook.call('jake', 18)