likes
comments
collection
share

根据tapable调试结果,手写SyncHook实现

作者站长头像
站长
· 阅读数 15

调试过程了解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);
    }

根据核心逻辑,手写实现,源码中一些方法有些谜之操作,可以用一些简便的方式来实现

  1. 实例化 hook , 定义 _x = [f1, f2, ...] taps = [{}, {}]
  2. 实例调用 tap taps = [{}, {}]
  3. 调用 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)

根据tapable调试结果,手写SyncHook实现