我们发现这两个complie也非常像,有大量重复代码,所以tapable为了解决这些重复代码,又进行了一次抽象,也就是代码工厂HookCodeFactory。HookCodeFactory的作用就是用来生成complie返回的call函数体,而HookCodeFactory在实现时也采用了Hook类似的思路,也是先实现了一个基类HookCodeFactory,然后不同的Hook再继承这个类来实现自己的代码工厂,比如SyncHookCodeFactory。
创建函数的方法在继续深入代码工厂前,我们先来回顾下JS里面创建函数的方法。一般我们会有这几种方法:
函数申明
function add(a, b) { return a + b; }
函数表达式
const add = function(a, b) { return a + b; }但是除了这两种方法外,还有种不常用的方法:使用Function构造函数。比如上面这个函数使用构造函数创建就是这样的:
const add = new Function('a', 'b', 'return a + b;');上面的调用形式里,最后一个参数是函数的函数体,前面的参数都是函数的形参,最终生成的函数跟用函数表达式的效果是一样的,可以这样调用:
add(1, 2); // 结果是3注意:上面的a和b形参放在一起用逗号隔开也是可以的:
const add = new Function('a, b', 'return a + b;'); // 这样跟上面的效果是一样的当然函数并不是一定要有参数,没有参数的函数也可以这样创建:
const sayHi = new Function('alert("Hello")'); sayHi(); // Hello这样创建函数和前面的函数申明和函数表达式有什么区别呢?使用Function构造函数来创建函数最大的一个特征就是,函数体是一个字符串,也就是说我们可以动态生成这个字符串,从而动态生成函数体。因为SyncHook和SyncBailHook的call函数很像,我们可以像拼一个字符串那样拼出他们的函数体,为了更简单的拼凑,tapable最终生成的call函数里面并没有循环,而是在拼函数体的时候就将循环展开了,比如SyncHook拼出来的call函数的函数体就是这样的:
"use strict"; var _x = this._x; var _fn0 = _x[0]; _fn0(newSpeed); var _fn1 = _x[1]; _fn1(newSpeed);上面代码的_x其实就是保存回调的数组taps,这里重命名为_x,我想是为了节省代码大小吧。这段代码可以看到,_x,也就是taps里面的内容已经被展开了,是一个一个取出来执行的。
而SyncBailHook最终生成的call函数体是这样的:
"use strict"; var _x = this._x; var _fn0 = _x[0]; var _result0 = _fn0(newSpeed); if (_result0 !== undefined) { return _result0; ; } else { var _fn1 = _x[1]; var _result1 = _fn1(newSpeed); if (_result1 !== undefined) { return _result1; ; } else { } }这段生成的代码主体逻辑其实跟SyncHook是一样的,都是将_x展开执行了,他们的区别是SyncBailHook会对每次执行的结果进行检测,如果结果不是undefined就直接return了,后面的回调函数就没有机会执行了。
创建代码工厂基类基于这个目的,我们的代码工厂基类应该可以生成最基本的call函数体。我们来写个最基本的HookCodeFactory吧,目前他只能生成SyncHook的call函数体:
class HookCodeFactory { constructor() { // 构造函数定义两个变量 this.options = undefined; this._args = undefined; } // init函数初始化变量 init(options) { this.options = options; this._args = options.args.slice(); } // deinit重置变量 deinit() { this.options = undefined; this._args = undefined; } // args用来将传入的数组args转换为New Function接收的逗号分隔的形式 // ['arg1', 'args'] ---> 'arg1, arg2' args() { return this._args.join(", "); } // setup其实就是给生成代码的_x赋值 setup(instance, options) { instance._x = options.taps.map(t => t); } // create创建最终的call函数 create(options) { this.init(options); let fn; // 直接将taps展开为平铺的函数调用 const { taps } = options; let code = ''; for (let i = 0; i < taps.length; i++) { code += ` var _fn${i} = _x[${i}]; _fn${i}(${this.args()}); ` } // 将展开的循环和头部连接起来 const allCodes = ` "use strict"; var _x = this._x; ` + code; // 用传进来的参数和生成的函数体创建一个函数出来 fn = new Function(this.args(), allCodes); this.deinit(); // 重置变量 return fn; // 返回生成的函数 } }上面代码最核心的其实就是create函数,这个函数会动态创建一个call函数并返回,所以SyncHook可以直接使用这个factory创建代码了:
// SyncHook.js const Hook = require('./Hook'); const HookCodeFactory = require("./HookCodeFactory"); const factory = new HookCodeFactory(); // COMPILE函数会去调用factory来生成call函数 const COMPILE = function(options) { factory.setup(this, options); return factory.create(options); }; function SyncHook(args = []) { const hook = new Hook(args); hook.constructor = SyncHook; // 使用HookCodeFactory来创建最终的call函数 hook.compile = COMPILE; return hook; } SyncHook.prototype = null; 让代码工厂支持SyncBailHook