動態

詳情 返回 返回

根據tapable調試結果,手寫SyncHook實現 - 動態 詳情

調試過程瞭解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)

image.png

user avatar chongdianqishi 頭像 guixiangyyds 頭像 weishiledanhe 頭像 jungang 頭像 xiaohe0601 頭像 ichu 頭像 prosuoqi 頭像 evilboy 頭像 lishisan 頭像 laughingzhu 頭像 qinyuanqiblog 頭像 amap_tech 頭像
點贊 28 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.