深度剖析SyncHook
先写一个简单的例子
class Dog{
constructor(
this.hooks = {
bark: new SyncHook()
}
)
}
此时bark成员就是一个标准的SyncHook对象了,那么这中间经历了什么呢?
首先new SyncHook()返回一个hook实例,并且将hook实例的compile对象重写。
//SyncHook.js
function SyncHook(args = [], name = undefined) {
let hook = new Hook(args, name);
hook.constructor = SyncHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
hook.compile = COMPILE;
return hook;
}
其中COMPILE做了两件事,分别是将options中tap的直接映射成tap.fn并赋值到SyncHook实例_x属性上,另一件是构建真正的Compile函数。
//SyncHook.js
const COMPILE = function(otpions) {
factory.setup(options);
return factory.create(options);
}
这就是初始化一个SyncHook过程发生的事情。
接下来是使用刚刚的hook。
let dog = new Dog();
dog.hooks.bark.tap("makeNoisePlugin", () => console.log("wang,wang..."))
其中"makeNoisePlugin"作为name参数可以用来区分插件/原因。
那么tap函数又发生了什么呢?
//Hook.js
tap(options, fn) {
this._tap("sync", options, fn);
}
可以看到tap将options和fn都作为参数传给了tap函数。
//Hook.js
_tap(type, options, fn) {
if(typeof options == "string") { //Step 1
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") { //step 2
deprecateContext();
}
options = Object.assign({ type, fn }, options); //step 3
options = this._runRegisterInterceptors(options); //step 4
this._insert(options); //step 5
}
在上面的处理中,结合我们的实例,可以看到options的变化
//step 1
options -> "warningPlugin" => { name: "warningPlugin" }
//step 2
如果options.context存在就会在控制台打印context已经被抛弃的警报信息
//step 3
options -> { name: "warningPlugin" } => { type: "sync", fn: fn, name: "warningPlugin" }
//step 4
根据拦截器进行调整options,这里没有拦截器暂时没有变化
//step 5
//这一步不会改动options本身
将options 赋值给this.taps[this.tap.length],this.taps是Hook类本身的成员对象,默认值为[]
下面是调用hook
dogs.hooks.bark.call();
调用call又是怎么样的过程呢?在Hook.js中构造函数是这样的
//Hook.js
const CALL_DELEGATE = function(...args) {
this.call = this._createCall("sync");
return this.call(...args);
}
constructor(args = [], name = undefined) {
this._call = CALL_DELEGATE;
this.call = CALL_DELEGATE;
}
可以看出,call先执行createCall返回函数,再执行。
//Hook.js
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.ineterceptors,
args: this._args,
type: type
})
}
这里可以看到实际上调用hook的时候,此时将options传入HookCodeFactory的create函数中执行,根据上面的实例可以看到实际调用内容为:
//HookCodeFactory.js
/**
* HookCodeFactor.create(options)
* options结构为{
* taps: [{ type: "sync", fn: fn, name: "warningPlugin" }],
* interceptors: [],
* args: [],
* type: "sync"
* }
*/
create(options) {
this.init(options); //step 1
let fn;
switch(this.options.type) {
case "sync":
fn = new Function(
this.args(), //step 2
'"use strict";\n' +
this.header() + //step 3
this.contentWithInterceptors({ //step 4
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
break;
case "async":
/** skip **/
}
this.deinit();
return fn;
}
//step 1
init(options) {
this.options = options; //将options赋值给实例的options
this._args = options.args.slice(); //返回一个新的数组对象给_args,断开this._args和options.args之间联系
}
//step 2
//本例中this._args = [],所以直接返回""
args({ before, after } = {}) {
let allArgs = this._args;
if (before) allArgs = [before].concat(allArgs);
if (after) allArgs = allArgs.concat(after);
if (allArgs.length === 0) {
return "";
} else {
return allArgs.join(", ");
}
}
//step 3
//这一步code = "var _context;\n var _x = this._x\n"
header() {
let code = "",
//needcontext主要通过遍历Options.taps,看tap中的context值是否为true,
//本例中为false
if(this.needContext()) {
code += "var _context = {};\n";
}else {
code += "var _context;\n";
}
code += "var _x = this._x\n";
if(this.options.interceptors.length > 0) {
code += "var _taps = this.taps;\n";
code += "var _interceptors = this.interceptors;\n";
}
return code;
}
//step 4
contentWithInterceptors(options) {
//本例中this.optins.interceptors.lenght = 0
if (this.options.interceptors.length > 0) {
const onError = options.onError;
const onResult = options.onResult;
const onDone = options.onDone;
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.call) {
code += `${this.getInterceptor(i)}.call(${this.args({
before: interceptor.context ? "_context" : undefined
})});\n`;
}
}
code += this.content(
Object.assign(options, {
onError:
onError &&
(err => {
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.error) {
code += `${this.getInterceptor(i)}.error(${err});\n`;
}
}
code += onError(err);
return code;
}),
onResult:
onResult &&
(result => {
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.result) {
code += `${this.getInterceptor(i)}.result(${result});\n`;
}
}
code += onResult(result);
return code;
}),
onDone:
onDone &&
(() => {
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.done) {
code += `${this.getInterceptor(i)}.done();\n`;
}
}
code += onDone();
return code;
})
})
);
return code;
} else {
//直接执行this.content(options),options
//注意此处的this.content是SyncHook的类成员函数
return this.content(options); //step 4-1
}
}
//step 4-1
//SyncHook.js
/**
* options结构为{
* onError: err => `throw ${err};\n`,
* onResult: result => `return ${result};\n`,
* resultReturns: true,
* onDone: () => "",
* rethrowIfPossible: true
* }
*/
content({onError, onDone, rethrowIfPossible }) {
//注意,callTapSeries是关键函数,扔到下面去写
return this.callTapSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
这里对HookCodeFactory类的callTapSeries重点整理
/**
* HookCodeFactory.js
* options结构为{
* onError: (i, err) => onError(err), //此处相当于onError: (i, err) => `throw ${err};\n`
* onResult: result => `return ${result};\n`,
* resultReturns: true,
* onDone: () => "",
* rethrowIfPossible: true
* }
*/
callTapsSeries({
onError,
onResult,
resultReturns,
onDone,
doneReturns,
rethrowIfPossible
}) {
//如果没有注册的插件,直接返回""
if (this.options.taps.length === 0) return onDone();
//firstAsync为 -1
const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
//本例中为true
const somethingReturns = resultReturns || doneReturns;
let code = "";
let current = onDone;
let unrollCounter = 0;
for (let j = this.options.taps.length - 1; j >= 0; j--) {
const i = j;
//false
const unroll =
current !== onDone &&
(this.options.taps[i].type !== "sync" || unrollCounter++ > 20);
if (unroll) {
unrollCounter = 0;
code += `function _next${i}() {\n`;
code += current();
code += `}\n`;
current = () => `${somethingReturns ? "return " : ""}_next${i}();\n`;
}
const done = current;
const doneBreak = skipDone => {
if (skipDone) return "";
return onDone();
};
//this.callTap 关键函数
//本例中i=0
const content = this.callTap(i, {
onError: error => onError(i, error, done, doneBreak),
onResult:
onResult &&
(result => {
return onResult(i, result, done, doneBreak);
}),
onDone: !onResult && done,
rethrowIfPossible:
rethrowIfPossible && (firstAsync < 0 || i < firstAsync)
});
//current = () => `var_fn0 = tap[0].fn;\n var _result0 = _fn0({}) return _result0`
current = () => content;
}
code += current();
return code;
}
//关键函数
//本例中 tapIdnex = 0,
/**
* {
* onError: (i, err, done, doneBreak) => `throw ${err};\n`,
* onResult: result => `return ${result};\n` && ( result => return onResult(i, result, done, doneBreak);),实际值为undefined
* onDone: !onResult && done, 实际为() => ""
* rethrowIfPossible: rethrowIfPossible && (firstAsync < 0 || i < firstAsync) 实际值为true
* }
*/
callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
let code = "";
let hasTapCached = false;
//本例中没有interceptors,直接跳过
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.tap) {
if (!hasTapCached) {
code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
hasTapCached = true;
}
code += `${this.getInterceptor(i)}.tap(${
interceptor.context ? "_context, " : ""
}_tap${tapIndex});\n`;
}
}
//在SyncHook创建对象时候,COMPILE中已经将创建的实例的_x属性设为Options.tap,此时tap内容为tap.fn,
//code += "var_fn0 = tap[0];\n"
code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
//tap = this.options.taps[0]
const tap = this.options.taps[tapIndex];
switch (tap.type) {
case "sync":
if (!rethrowIfPossible) {
code += `var _hasError${tapIndex} = false;\n`;
code += "try {\n";
}
//code += "_fn0({})"
if (onResult) {
code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
} else {
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
}
if (!rethrowIfPossible) {
code += "} catch(_err) {\n";
code += `_hasError${tapIndex} = true;\n`;
code += onError("_err");
code += "}\n";
code += `if(!_hasError${tapIndex}) {\n`;
}
//code += onResult("_result0") = "return _result0";
if (onResult) {
code += onResult(`_result${tapIndex}`);
}
if (onDone) {
code += onDone();
}
if (!rethrowIfPossible) {
code += "}\n";
}
break;
case "async":
/** skip **/
}
return code;
}
总结,code最后内容为
code = `var _context;\n var _x = this._x\n var_fn0 = tap[0];\n _fn0({});\n\n`;
//在HookCodeFactory中setup将tap数组直接处理为tap.fn数组,即tap = tap.fn, 所以实际上_x = tap,
//即code又可以写为
code = `var _context;\n var _x = this._x\n var_fn0 = _x[0];\n _fn0({});\n\n`;
fn = new Function(code);
这是实际调试之后得到的内容,基本正确。

由上面得到call的函数fn之后,在进行调用fn(),返回结果。
fn() === "wang,wang..." // true;
经过调式后发现上面对于输出的判断是错误的。声明文件中SyncHook的声明对call函数描述是这样的:
export class SyncHook<T, R = void, AdditionalOptions = UnsetAdditionalOptions> extends Hook<T, R, AdditionalOptions> {
call(...args: AsArray<T>): R;
}
call的返回类型是R,R默认为void。所以当代码为下面时,此时返回值为undefined
console.log(fn()) // undefined

本文深入剖析了JavaScript中的SyncHook,从初始化、实例化到使用过程进行了详细解释。SyncHook如何将函数映射到实例属性,以及在调用过程中如何执行这些函数。文章还探讨了call方法的工作原理,通过调试揭示了SyncHook的实际运行情况和可能的误解。最终,文章总结了SyncHook的核心要点,并指出了官方声明文件中关于返回类型的细节。
843

被折叠的 条评论
为什么被折叠?



