懒编译(延迟编译):每个Hook在创建的时候有一个call方法,但一直等到调用它的时候才将它动态改写成触发各个回调的方法,之后的执行依然使用改写后的方法,这个过程通过代理类函数,如CALL_DELEGATE(call的代理)来实现

    1. const CALL_DELEGATE = function (...args) {
    2. this.call = this._createCall('sync');
    3. return this.call(...args);
    4. }
    5. class Hook {
    6. constructor(args = []) {
    7. this.args = args;//['name','age']
    8. this.taps = [];//就是一个存放我们事件函数的数组,订阅把函数存起来,存到这个数组里去了 {name,fn}
    9. this.call = CALL_DELEGATE;//this.taps.map(tap=>tap.fn)=this._x
    10. this.callAsync = CALL_ASYNC_DELEGATE;
    11. this.promise = PROMISE_DELEGATE;//promise方法
    12. this._x = undefined;//其实才是真正存放我们事件函数的数组 [fn]
    13. this.interceptors = [];//这就是拦截器
    14. }
    15. }

    如果在hook实例调用过call之后,又重新添加了回调,则需要重新编译:
    下面的代码中再第一次syncHook.call之后,又通过tap订阅了一个名为’4’的方法,再次调用了syncHook.call

    1. const { SyncHook } = require('./tapable');
    2. //参数是一个数组,参数长度有用,代表取真实的call的参数的个数,数组里字符串的名字没用
    3. const syncHook = new SyncHook(['name','age']);
    4. //注册事件函数
    5. syncHook.tap({name:'1'}, (name, age) => { // events on
    6. console.log(1, name, age);
    7. });
    8. syncHook.tap('2', (name, age) => {
    9. console.log(2, name, age);
    10. });
    11. syncHook.tap('3', (name, age) => {
    12. console.log(3, name, age);
    13. });
    14. //触发事件函数
    15. syncHook.call('zhufeng', 12); // emit
    16. syncHook.tap('4', (name, age) => {
    17. console.log(4, name, age);
    18. });
    19. syncHook.call('zhufeng', 12);

    代码中对应的处理是_resetCompilation方法:

    class Hook {
        constructor(args = []) {
            this.args = args;//['name','age']
            this.taps = [];//就是一个存放我们事件函数的数组,订阅把函数存起来,存到这个数组里去了 {name,fn}
            this.call = CALL_DELEGATE;//this.taps.map(tap=>tap.fn)=this._x
            this.callAsync = CALL_ASYNC_DELEGATE;
            this.promise = PROMISE_DELEGATE;//promise方法
            this._x = undefined;//其实才是真正存放我们事件函数的数组 [fn]
            this.interceptors = [];//这就是拦截器
        }
        tap(options, fn) {
            this._tap('sync', options, fn);
        }
        _tap(type, options, fn) {
            if (typeof options === "string") {
                options = { name: options };
            }
            //创建tapInfo并且插入到数组中去
            let tapInfo = { ...options, type, fn };
            tapInfo = this._runRegisterInterceptors(tapInfo);
            this._insert(tapInfo);
        }
        _insert(tapInfo) {
            this._resetCompilation();//每次插入新的函数,需要重新编译call方法
              this.taps.push(tapInfo);
        }
    }
    

    不同类型的Hook会有各自的compile方法,会调用HookCodeFactory生成模板代码,HookCodeFactory会接收Hook对象的taps数组(订阅数组)、Hook类型等作为参数(代码中体现为options参数):

    let Hook = require('./Hook');
    const HookCodeFactory = require('./HookCodeFactory');
    class SyncHookCodeFactory extends HookCodeFactory{
        //内容不一样
        content({onDone}){
            //串行调用taps函数  fn0() fn1() fn2()
            return this.callTapsSeries({onDone});
        }
    }
    let factory = new SyncHookCodeFactory();
    class SyncHook extends Hook{
        compile(options){
            //把钩子的实例和选项的值用来初始化代码工厂
            factory.setup(this,options);//options={type:'sync',taps,args}
            //根据选项创建call方法 new Function(args,函数体taps);
            return factory.create(options);
        }
    }
    
    module.exports = SyncHook;
    

    HookCodeFactory完整代码:

    class HookCodeFactory {
        setup(hookInstance, options) {
            //把tapInfo中的fn取出变成数组赋值给hookInstance._x
            hookInstance._x = options.taps.map(tapInfo => tapInfo.fn);
        }
        args(options = {}) {//{taps,args,type}
            let { before, after } = options;
            let allArgs = this.options.args || [];//args = ['name','age'];
            if (before) allArgs = [before, ...allArgs];
            if (after) allArgs = [...allArgs, after];
            return allArgs.join(',');//name,age
        }
        header() {
            let code = '';
            code += `var _x = this._x;\n`;
            return code;
        }
        callTapsSeries({ onDone } = {}) {
            let taps = this.options.taps;
            if (taps.length === 0) {
                return '';
            }
            let code = '';
            for (let i = 0; i < taps.length; i++) {
                const content = this.callTap(j);
                    code += content;
            }
            return code;
        }
        //onDone是每一个事件函数执行后的回调
        callTap(tapIndex, { onDone }) {
            //var _fn0 = _x[0];_fn0(name, age);
            let code = '';
            code += `var _fn${tapIndex} = _x[${tapIndex}];\n`;
            let tapInfo = this.options.taps[tapIndex];//{name,fn,type}
            switch (tapInfo.type) {
                case 'sync':
                    code += `_fn${tapIndex}(${this.args()})\n`;
                      code += onDone();
                    break;
                default:
                    break;
            }
            return code;
        }
        init(options) {
            this.options = options;
        }
        deinit() {
            this.options = null;
        }
        create(options) {
            this.init(options);
            let fn;
            switch (options.type) {//sync
                case 'sync'://同步
                    fn = new Function(
                        this.args(),//name,age
                        this.header() + this.content({onDone:()=>""})
                    );
                    break;
                default:
                    break;
            }
            this.deinit();
            return fn;
        }
    
    }
    module.exports = HookCodeFactory;
    

    Hook完整代码:

    const CALL_DELEGATE = function (...args) {
        //先动态创建一个sync同步的类型的call方法,然后赋值给this.call
        //关于this的问题,只有一句话记住就行了.谁调用它就指向谁 钩子的实例syncHook.call('zhufeng', 12);
        this.call = this._createCall('sync');
        return this.call(...args);
    }
    class Hook {
        constructor(args = []) {
            this.args = args;//['name','age']
            this.taps = [];//就是一个存放我们事件函数的数组,订阅把函数存起来,存到这个数组里去了 {name,fn}
            this.call = CALL_DELEGATE;//this.taps.map(tap=>tap.fn)=this._x
            this.callAsync = CALL_ASYNC_DELEGATE;
            this.promise = PROMISE_DELEGATE;//promise方法
            this._x = undefined;//其实才是真正存放我们事件函数的数组 [fn]
            this.interceptors = [];//这就是拦截器
        }
        tap(options, fn) {
            this._tap('sync', options, fn);
        }
        _tap(type, options, fn) {
            if (typeof options === "string") {
                options = { name: options };
            }
            //创建tapInfo并且插入到数组中去
            let tapInfo = { ...options, type, fn };
            this._insert(tapInfo);
        }
        _insert(tapInfo) {
              this.taps.push(tapInfo);
        }
        compile(options) {
            throw new Error('Abstract:此方法应该被子类重写');
        }
        _createCall(type) {
            //动态创建一个函数
            return this.compile({
                taps: this.taps,//执行函数的事件函数
                args: this.args,//事件函数接收的参数
                type,//执行的类型 sync  async
            });
        }
    }
    
    module.exports = Hook;
    

    综上,钩子在触发时的调用顺序依次为:

    sysHook.call('zhufeng', 12)
    // 由于Hook对象在初始化时做了this.call = CALL_DELEGATE这一操作
    // 所以实际执行了CALL_DELEGATE
    
    // 然后又将this._createCall('sync')赋值给this.call:
    this.call = this._createCall('sync')
    
    // this._createCall又调用了compile方法:
    this.compile({
        taps: this.taps,//执行函数的事件函数
        args: this.args,//事件函数接收的参数
        type,//执行的类型 sync  async
    })
    
    // 接下来调用各自Hook的子类对应的compile方法
    // compile内部会先调用factory的setup方法,factory对象是先前实例化好的
    
    // setup主要是给hookInstance添加_x属性,存储所有订阅的函数:
    hookInstance._x = options.taps.map(tapInfo => tapInfo.fn)
    
    // 接下来,compile里面会继续调用factory.create(options)
    // 并将此调用的返回值作为compile的返回值
    factory.create(options)
    
    // factory.create内部会拼接组成函数的各个片段
    // 这个过程会调用this.args、this.header、this.content这些方法分别去拼接各个部分
    new Function(
        this.args(),//name,age
        this.header() + this.content({onDone:()=>""})
    )
    
    // 需要注意this.content是在HookCodeFactory父类中调用了子类(例如SyncHookCodeFactory)的方法
    

    对于同步的钩子,想要拼接出类似这样的结构:

    (function anonymous(name, age) {
        //是一个数组,里面存放着我们的所有的事件函数
        var _x = this._x;
    
        var _fn0 = _x[0];
        _fn0(name, age);
    
        var _fn1 = _x[1];
        _fn1(name, age);
    
        var _fn2 = _x[2];
        _fn2(name, age);
    })
    

    对于异步并行的钩子,想要拼接出这样的结构:

    (function anonymous(name, age, _callback) {
        var _x = this._x;
        var _counter = 3;
        var _done = (function () {//所有的任务都完成了调用_done方法,从而调用最终的回调
            _callback();
        });
    
        var _fn0 = _x[0];
        _fn0(name, age, (function () {
            if (--_counter === 0) _done();
        }));
    
        var _fn1 = _x[1];
        _fn1(name, age, (function () {
            if (--_counter === 0) _done();
        }));
    
        var _fn2 = _x[2];
        _fn2(name, age, (function () {
            if (--_counter === 0) _done();
        }));
    })
    

    异步并行的钩子类似于Promise.all
    异步并行的调用方式可以是callAsync和promise

    hook.tapAsync('1', (name, age, callback) => {
        setTimeout(() => {
            console.log(1, name, age);
            callback();
        }, 1000);
    });
    hook.tapAsync('2', (name, age, callback) => {
        setTimeout(() => {
            console.log(1, name, age);
            callback();
        }, 2000);
    });
    hook.tapAsync('3', (name, age, callback) => {
        setTimeout(() => {
            console.log(1, name, age);
            callback();
        }, 3000);
    });
    
    hook.callAsync('zhufeng', 12, (err) => {
        console.log('err', err);
        console.timeEnd('cost');
    });
    
    hook.tapPromise('1', (name, age) => {
        return new Promise((resolve,reject)=>{
            setTimeout(() => {
                console.log(1, name, age);
                resolve();
            }, 1000);
        });
    });
    hook.tapPromise('2', (name, age,) => {
        return new Promise((resolve,reject)=>{
            setTimeout(() => {
                console.log(2, name, age);
                resolve();
            }, 2000);
        });
    });
    hook.tapPromise('3', (name, age,) => {
        return new Promise((resolve,reject)=>{
            setTimeout(() => {
                console.log(3, name, age);
                resolve();
            }, 3000);
        });
    });
    hook.promise('zhufeng', 12).then(result=>{
        console.log(result);
        console.timeEnd('cost');
    });
    

    需要添加对异步并行的处理:

        callTapsParallel() {
            let code = '';
            code += `var _counter = ${this.options.taps.length};\n`;
            code += `var _done = (function () {
                _callback();
            });\n`;
            for (let i = 0; i < this.options.taps.length; i++) {
                let content = this.callTap(i, {});
                code += content;
            }
            return code;
        }
    
        callTap(tapIndex) {
            //var _fn0 = _x[0];_fn0(name, age);
            let code = '';
            code += `var _fn${tapIndex} = _x[${tapIndex}];\n`;
            let tapInfo = this.options.taps[tapIndex];//{name,fn,type}
            switch (tapInfo.type) {
                case 'sync':
                    code += `_fn${tapIndex}(${this.args()})\n`;
                      code += onDone();
                    break;
                case 'async':
                    code += `_fn${tapIndex}(${this.args({
                        after: ` function () {
                            if (counter === 0) _done();
                        }`
                    })});\n`;
                    break;
    

    异步串行,调用方式和异步并行是一样的
    需要拼接的代码:

    (function anonymous(name, age, _callback) {
        var _x = this._x;
    
        function _next1() {
            var _fn2 = _x[2];
            _fn2(name, age, (function () {
                _callback();
            }));
        }
        function _next0() {
            var _fn1 = _x[1];
            _fn1(name, age, (function () {
                _next1();
            }));
        }
        var _fn0 = _x[0];
        _fn0(name, age, (function () {
            _next0();
        }));
    })
    

    异步串行用的和同步一样的callTapsSeries方法,需要对其进行改造:
    改造的原则:
    从上面的模板代码中可以发现,_next1、_next0的定义顺序是倒序的,所以会有一个倒序的循环
    _next1、_next0的函数体里面,分别调用_fn2、_fn1的回调中,执行的方法分别是_callback _next1,是各不相同的,所以,需要将callTap中拼接回调的部分做成动态的

        callTapsSeries({ onDone } = {}) {
            let taps = this.options.taps;
            if (taps.length === 0) {
                return onDone(); //_callback();
            }
            let code = '';
            let current = onDone;
            for (let j = taps.length - 1; j >= 0; j--) {
                const unroll = current !== onDone;//如果不是最终的执行函数
                if (unroll) {//0=>1=>2
                    //1.在外层 包裹 next函数
                    //2.决定 下一个事件函数的onDone是自己这个next
                    code += `function _next${j}(){\n`;
                    code += current();
                    code += '}\n';
                    current = () => `_next${j}();\n`;
                }
                const done = current;
                const content = this.callTap(j, { onDone: done });
                current = () => content;
            }
            code += current();
            return code;
        }
    

    callTap中的参数onDone是每执行一个回调,要执行的回调
    所有回调执行完时,也有一个onDone回调,上面的callTapSeries中的参数onDone就是所有回调执行完时调用的回调,上面的callTapSeries中for循环上面的current变量,实际上是用来跟踪执行当前callTap的onDone回调,这两个回调在最开始是一样的

        callTap(tapIndex, {onDone}) {
            //var _fn0 = _x[0];_fn0(name, age);
            let code = '';
            code += `var _fn${tapIndex} = _x[${tapIndex}];\n`;
            let tapInfo = this.options.taps[tapIndex];//{name,fn,type}
            switch (tapInfo.type) {
                case 'sync':
                    code += `_fn${tapIndex}(${this.args()})\n`;
                      code += onDone();
                    break;
                case 'async':
                    code += `_fn${tapIndex}(${this.args({
                        after: ` function () {
                            ${onDone()}
                        }`
                    })});\n`;
                    break;
    

    接下来我们分析一下,callTapsSeries中for循环每次执行,current函数是什么,code变成了什么:
    调用了this.callTap(j, { onDone: done }),j === 2 的这轮for循环结束时

    current === ()=>`
    var _fn2 = _x[2];
    _fn2(name, age, (function () {
        _callback();
    }));
    `
    
    code === ''
    

    接下来进入j === 1的这轮循环,if中的代码执行完后

    current === ()=>`_next1();\n`;
    
    code === `
    function _next1(){
        var _fn2 = _x[2];
        _fn2(name, age, (function () {
            _callback();
        }));
    }
    `
    

    j === 1这轮循环结束时

    current === ()=>`
    var _fn1 = _x[1];
    _fn1(name, age, (function () {
        _next1();
    }));`
    

    接下来进入j === 0的这轮循环,if中的代码执行完后

    current === ()=>`_next0();\n`;
    
    code === `
    function _next1(){
        var _fn2 = _x[2];
        _fn2(name, age, (function () {
            _callback();
        }));
    }
    
    function _next0() {
        var _fn1 = _x[1];
        _fn1(name, age, (function () {
            _next1();
        }));
    }
    `
    

    j === 0这轮循环结束时

    current === () => `
    var _fn0 = _x[0];
    _fn0(name, age, (function () {
        _next0();
    }));
    `
    

    此时整个for循环也就结束了,for循环后面我们可以看到执行了code += current(),因此code会拼上最后这部分代码,最终变成:

    code === `
    function _next1(){
        var _fn2 = _x[2];
        _fn2(name, age, (function () {
            _callback();
        }));
    }
    
    function _next0() {
        var _fn1 = _x[1];
        _fn1(name, age, (function () {
            _next1();
        }));
    }
    
    var _fn0 = _x[0];
    _fn0(name, age, (function () {
        _next0();
    }));
    `