懒编译(延迟编译):每个Hook在创建的时候有一个call方法,但一直等到调用它的时候才将它动态改写成触发各个回调的方法,之后的执行依然使用改写后的方法,这个过程通过代理类函数,如CALL_DELEGATE(call的代理)来实现
const CALL_DELEGATE = function (...args) {
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 = [];//这就是拦截器
}
}
如果在hook实例调用过call之后,又重新添加了回调,则需要重新编译:
下面的代码中再第一次syncHook.call之后,又通过tap订阅了一个名为'4'的方法,再次调用了syncHook.call
const { SyncHook } = require('./tapable');
//参数是一个数组,参数长度有用,代表取真实的call的参数的个数,数组里字符串的名字没用
const syncHook = new SyncHook(['name','age']);
//注册事件函数
syncHook.tap({name:'1'}, (name, age) => { // events on
console.log(1, name, age);
});
syncHook.tap('2', (name, age) => {
console.log(2, name, age);
});
syncHook.tap('3', (name, age) => {
console.log(3, name, age);
});
//触发事件函数
syncHook.call('zhufeng', 12); // emit
syncHook.tap('4', (name, age) => {
console.log(4, name, age);
});
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();
}));
`