深度剖析SyncHook

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深度剖析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做了两件事,分别是将optionstap的直接映射成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);
}

可以看到tapoptionsfn都作为参数传给了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传入HookCodeFactorycreate函数中执行,根据上面的实例可以看到实际调用内容为:

//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的返回类型是RR默认为void。所以当代码为下面时,此时返回值为undefined

console.log(fn()) // undefined
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值