webpack4核心模块tapable源码解析

阅读目录php

webpack打包是一种事件流的机制,它的原理是将各个插件串联起来,那么实现这一切的核心就是咱们要讲解的tapable. 而且在webpack中负责编译的Compiler和负责建立bundles的Compilation都是tapable构造函数的实列。html

WebPack的loader(加载器)和plugin(插件)是由Webpack开发者和社区开发者共同贡献的。若是咱们想写加载器和插件的话,咱们须要看懂Webpack的基本原理,也就是说要看懂Webpack源码,咱们今天要讲解的 tapable 则是webpack依赖的核心库。所以在看懂webpack以前,咱们先要把 tapable 这个代码看懂。node

我这边讲解的是基于 "webpack": "^4.16.1",这个版本的包的。安装该webpack以后,该webpack会自带 tapable 包。在tapable包下它是由以下js文件组成的。webpack

|---- tapable |  |--- AsyncParallelBailHook.js |  |--- AsyncParallelHook.js |  |--- AsyncSeriesBailHook.js |  |--- AsyncSeriesHook.js |  |--- AsyncSeriesLoopHook.js |  |--- AsyncSeriesWaterfallHook.js |  |--- Hook.js |  |--- HookCodeFactory.js |  |--- HookMap.js |  |--- index.js |  |--- MultiHook.js |  |--- simpleAsyncCases.js |  |--- SyncBailHook.js |  |--- SyncHook.js |  |--- SyncLoopHook.js |  |--- SyncWaterfallHook.js |  |--- Tapable.js

以下图所示:git

Tapable的本质是能控制一系列注册事件之间的执行流的机制。如上图咱们能够看到,都是以Sync, Async 及 Hook结尾的方法,他们为咱们提供了不一样的事件流执行机制,咱们能够把它叫作 "钩子"。那么这些钩子能够分为2个类别,即 "同步" 和 "异步", 异步又分为两个类别,"并行" 仍是 "串行",同步的钩子它只有 "串行"。github

以下图所示:web

下面我会把全部的测试demo放在咱们的项目结构下的 public/js/main.js 文件代码内,咱们能够执行便可看到效果。下面是咱们项目中的目录基本结构(很简单,无非就是一个很简单的运行本地demo的框架):
github中demo框架 算法

能够把该框架下载到本地来,下面的demo可使用该框架来测试代码了。json

|--- tapable项目 | |--- node_modules | |--- public | | |--- js | | | |--- main.js | |--- package.json | |--- webpack.config.js

一:理解Sync类型的钩子数组

1. SyncHook.js

SyncHook.js 是处理串行同步执行的文件,在触发事件以后,会按照事件注册的前后顺序执行全部的事件处理函数。

以下代码所示:

const { SyncHook } = require('tapable'); // 建立实列
const syncHook = new SyncHook(["name", "age"]); // 注册事件
syncHook.tap("1", (name, age) => { 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("kongzhiEvent-1", 18);

执行的结果以下所示:

如上demo实列能够看到,在咱们的tapable中,SyncHook是tapable中的一个类,首先咱们须要建立一个实列,注册事件以前须要建立实列,建立实列时须要传入一个数组,该数组的存储的事件是咱们在注册事件时,须要传入的参数。实列中的tap方法用于注册事件,该方法支持传入2个参数,第一个参数是 '事件名称', 第二个参数为事件处理函数,函数参数为执行call(触发事件)时传入的参数的形参。

2. SyncBailHook.js

SyncBailHook.js一样为串行同步执行,若是事件处理函数执行时有一个返回值不为空。则跳过剩下未执行的事件处理函数。

以下代码所示:

const { SyncBailHook } = require('tapable'); // 建立实列
 const syncBailHook = new SyncBailHook(["name", "age"]); // 注册事件
syncBailHook.tap("1", (name, age) => { console.log("1", name, age); }); syncBailHook.tap("2", (name, age) => { console.log("2", name, age); return '2'; }); syncBailHook.tap("3", (name, age) => { console.log("3", name, age); }); // 触发事件,让监听函数执行
syncBailHook.call("kongzhiEvent-1", 18);

以下图所示:

如上代码咱们能够看到,第一个注册事件,直接执行打印 console.log(); 打印信息出来,它会继续执行第二个事件,第二个注册事件有 return '2'; 有返回值,且返回值不为undefined,所以它会跳事后面的注册事件。所以如上就打印2条信息了。也就是说 syncBailHook 做用也是同步执行的,只是说若是咱们的注册事件的回调函数有返回值,且返回值不为undefined的话,那么它就会跳事后面的注册事件。即马上中止执行后面的监听函数。

3. SyncWaterfallHook.js

SyncWaterfallHook 为串行同步执行,上一个事件处理函数的返回值做为参数传递给下一个事件处理函数,依次类推。

以下测试代码: 

const { SyncWaterfallHook } = require('tapable'); // 建立实列
const syncWaterfallHook = new SyncWaterfallHook(["name", "age"]); // 注册事件
syncWaterfallHook.tap("1", (name, age) => { console.log("第一个函数事件名称", name, age); return '1'; }); syncWaterfallHook.tap("2", (data) => { console.log("第二个函数事件名称", data); return '2'; }); syncWaterfallHook.tap("3", (data) => { console.log("第三个函数事件名称", data); return '3'; }); // 触发事件,让监听函数执行
const res = syncWaterfallHook.call("kongzhiEvent-1", 18); console.log(res);

打印信息以下所示:

4. SyncLoopHook.js

SyncLoopHook 为串行同步执行,事件处理函数返回true表示继续循环,若是返回undefined的话,表示结束循环。

以下代码演示:

const { SyncLoopHook } = require('tapable'); // 建立实列
const syncLoopHook = new SyncLoopHook(["name", "age"]); // 定义辅助变量
let total1 = 0; let total2 = 0; // 注册事件
syncLoopHook.tap("1", (name, age) => { console.log("1", name, age, total1); return total1++ < 2 ? true : undefined; }); syncLoopHook.tap("2", (name, age) => { console.log("2", name, age, total2); return total2++ < 2 ? true : undefined; }); syncLoopHook.tap("3", (name, age) => { console.log("3", name, age); }); // 触发事件,让监听函数执行
syncLoopHook.call("kongzhiEvent-1", 18);

执行的结果以下所示:

执行结果如上所示,咱们来理解下 SyncLoopHook 执行顺序,首先咱们知道,SyncLoopHook 的基本原理是:事件处理函数返回true表示继续循环,若是返回undefined的话,表示结束循环。

首先咱们出发第一个注册事件函数,total1 依次循环,因此会打印 0, 1, 2 的值,所以打印的值以下所示:

1 kongzhiEvent-1 18 0
1 kongzhiEvent-1 18 1
1 kongzhiEvent-1 18 2

当 total1 = 2 的时候,再去判断 2 < 2 呢?因此最后值返回 undefined, 所以就执行第二个回调函数,可是此时因为 total1++; 所以 total1 = 3了。因此执行完成第二个 函数的时候,会打印信息以下:

2 kongzhiEvent-1 18 0

可是因为等于true,因此会继续循环函数,所以又会从第一个函数内部训话,所以第一个函数就会打印以下信息:

1 kongzhiEvent-1 18 3

可是因为 total1 = 3了,所以又返回undefined了,所以又会执行 第二个函数,这个时候 total2 = 1了,所以会打印:

2 kongzhiEvent-1 18 1

而后返回true,继续从第一个函数循环执行,此时的 total1 = 4; 由于在上次 total1=3 虽然条件不知足,可是仍是会自增1的,所以会继续循环打印以下信息:

1 kongzhiEvent-1 18 4

此时 又不知足,所以会执行第二个函数,此时的 total2=2了,由于在上一次执行完成后,total2会自增1. 所以先打印以下信息:

2 kongzhiEvent-1 18 2

因为此时 total2 = 2; 所以最后返回undefined,所以会执行第三个函数,可是此时的 total2 = 3了,由于执行了 total2++; 因此最后一个函数会打印以下信息:

3 kongzhiEvent-1 18

如上就是 SyncLoopHook.js 函数的做用。

二:理解Async类型的钩子

Async类型可使用tap, tapSync 和 tapPromise 注册不一样类型的插件钩子,咱们分别能够经过 call, callAsync, promise 方法调用。

1. AsyncParallelHook

AsyncParallelHook 为异步并行执行,若是是经过 tapAsync 注册的事件,那么咱们须要经过callAsync触发,若是咱们经过tapPromise注册的事件,那么咱们须要promise触发。

1)tapAsync/callAsync

以下代码所示:

const { AsyncParallelHook } = require('tapable'); // 建立实列
const asyncParallelHook = new AsyncParallelHook(["name", "age"]); // 注册事件
asyncParallelHook.tapAsync("1", (name, age, done) => { setTimeout(() => { console.log("1", name, age, new Date()); done(); }, 1000); }); asyncParallelHook.tapAsync("2", (name, age, done) => { setTimeout(() => { console.log("2", name, age, new Date()); done(); }, 2000); }); asyncParallelHook.tapAsync("3", (name, age, done) => { setTimeout(() => { console.log("3", name, age, new Date()); done(); }, 3000); }); // 触发事件,让监听函数执行
asyncParallelHook.callAsync("kongzhiEvent-1", 18, () => { console.log('函数执行完毕'); });

执行结果以下所示:

如上咱们能够看到,该三个函数,第一个函数是 2019 17:10:55,第二个函数是 2019 17:10:56,第三个函数是 2019 17:10:57 执行完成了,上面三个函数定时器操做最长的时间也是3秒,咱们把这三个函数执行完成总共也是使用了3秒的时间,说明了该三个事件处理函数是异步执行的了。不须要等待上一个函数结束后再执行下一个函数。

tapAsync注册的事件函数最后一个参数为回调函数done,每一个事件处理函数在异步代码执行完成后都会调用该done函数。所以就能保证咱们的 callAsync会在全部异步函数执行完毕后就执行该回调函数。

2)tapPromise/promise

使用tapPromise注册的事件,必须返回一个Promise实列,promise方法也会返回一个Promise实列。

以下代码演示:

const { AsyncParallelHook } = require('tapable'); // 建立实列
const asyncParallelHook = new AsyncParallelHook(["name", "age"]); // 注册事件
asyncParallelHook.tapPromise("1", (name, age) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log("1", name, age, new Date()); }, 1000); }); }); asyncParallelHook.tapPromise("2", (name, age) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log("2", name, age, new Date()); }, 2000); }); }); asyncParallelHook.tapPromise("3", (name, age) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log("3", name, age, new Date()); }, 3000); }); }); // 触发事件,让监听函数执行
asyncParallelHook.promise("kongzhiEvent-1", 18);

效果以下所示:

如上代码所示,每一个tabPromise注册事件的处理函数都会返回一个Promise实列,新版的代码可能改掉了,咱们不能再promise函数内部使用 resolve('1') 这样的,在外部不能使用 asyncParallelHook.promise("kongzhiEvent-1", 18).then() 这样的,不支持then,刚看了源码,也没有返回一个新的promise对象。

如上也能够看到,咱们的第一个函数须要1秒后执行,第二个函数须要2秒后执行,第三个函数须要三秒后执行,可是咱们打印的信息能够看到,总共花费了3秒时间,也就是说咱们上面的三个函数也是并行执行的。并非须要等前一个函数执行完毕后再执行后面的函数。

2. AsyncSeriesHook

AsyncSeriesHook 为异步串行执行的。和咱们上面的 AsyncParallelHook同样,经过使用 tapAsync注册事件,经过callAsync触发事件,也能够经过 tapPromise注册事件,使用promise来触发。

1)tapAsync/callAsync

和咱们上面的 AsyncParallelHook同样, AsyncParallelHook 的 callAysnc方法也是经过传入回调函数的方式,在全部事件函数处理完成后,咱们须要执行 callAsync的回调函数。

以下代码演示:

const { AsyncSeriesHook } = require('tapable'); // 建立实列
const asyncSeriesHook = new AsyncSeriesHook(["name", "age"]); // 注册事件
asyncSeriesHook.tapAsync("1", (name, age, done) => { setTimeout(() => { console.log("1", name, age, new Date()); done(); }, 1000); }); asyncSeriesHook.tapAsync("2", (name, age, done) => { setTimeout(() => { console.log("2", name, age, new Date()); done(); }, 2000); }); asyncSeriesHook.tapAsync("3", (name, age, done) => { setTimeout(() => { console.log("3", name, age, new Date()); done(); }, 3000); }); // 触发事件,让监听函数执行
asyncSeriesHook.callAsync("kongzhiEvent-1", 18, () => { console.log('执行完成'); });

执行结果以下所示:

从上面打印信息咱们能够看到,咱们第一次打印 1 kongzhiEvent-1 18 Mon Aug 05 2019 17:58:09 GMT+0800 (中国标准时间) 这个信息,而后当咱们执行第二个函数的时候,是2000毫秒后执行,所以打印第二条信息以下所示:

2 kongzhiEvent-1 18 Mon Aug 05 2019 17:58:11 GMT+0800 (中国标准时间)

接着咱们执行第三个函数,隔了3000毫秒后执行,所以能够看到打印信息以下:

3 kongzhiEvent-1 18 Mon Aug 05 2019 17:58:14 GMT+0800 (中国标准时间)

所以咱们能够看到该方法是串行执行的。

2)tapPromise/promise

和上面的 AsyncParallelHook 同样,使用tapPromise来注册事件函数,而后须要返回一个Promise实列,而后咱们使用 promise 来触发该事件。

以下代码所示:

const { AsyncSeriesHook } = require('tapable'); // 建立实列
const asyncSeriesHook = new AsyncSeriesHook(["name", "age"]); // 注册事件
asyncSeriesHook.tapPromise("1", (name, age) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log("1", name, age, new Date()); resolve(); }, 1000); }) }); asyncSeriesHook.tapPromise("2", (name, age) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log("2", name, age, new Date()); resolve(); }, 2000); }); }); asyncSeriesHook.tapPromise("3", (name, age) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log("3", name, age, new Date()); resolve(); }, 3000); }); }); // 触发事件,让监听函数执行
asyncSeriesHook.promise("kongzhiEvent-1", 18);

如上代码执行效果以下所示:

如上代码,咱们对比 AsyncParallelHook 代码能够看到,惟一不一样的是 该asyncSeriesHook的Promsie内部须要调用 resolve() 函数才会执行到下一个函数,不然的话,只会执行第一个函数,可是 AsyncParallelHook 不调用 resolve()方法会依次执行下面的函数。

三:tapable源码分析

  先以SyncHook.js 源码分析:
 源码以下:
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */
"use strict"; const Hook = require("./Hook"); const HookCodeFactory = require("./HookCodeFactory"); class SyncHookCodeFactory extends HookCodeFactory { content({ onError, onResult, onDone, rethrowIfPossible }) { return this.callTapsSeries({ onError: (i, err) => onError(err), onDone, rethrowIfPossible }); } } const factory = new SyncHookCodeFactory(); class SyncHook extends Hook { tapAsync() { throw new Error("tapAsync is not supported on a SyncHook"); } tapPromise() { throw new Error("tapPromise is not supported on a SyncHook"); } compile(options) { factory.setup(this, options); return factory.create(options); } } module.exports = SyncHook;

如上代码咱们能够看到 SyncHook 类它继承了 Hook 类,而后 定义了 SyncHookCodeFactory 类 继承了 HookCodeFactory 类,咱们先来看看 Hook.js 相关的代码以下:

class Hook { constructor(args) { // args 参数必须是一个数组,好比咱们上面的demo 传递值为 ["name", "age"]
    if(!Array.isArray(args)) args = []; // 把数组args赋值给 _args的内部属性
    this._args = args; // 保存全部的tap事件
    this.taps = []; // 拦截器数组
    this.interceptors = []; // 调用 内部方法 _createCompileDelegate 而后把返回值赋值给内部属性 _call, 而且暴露给外部属性 call
    this.call = this._call = this._createCompileDelegate("call", "sync"); /* 调用 内部方法 _createCompileDelegate ,而后把返回值赋值给内部属性 _promise,而且暴露外部属性 promise */
    this.promise = this._promise = this._createCompileDelegate("promise", "promise"); /* 调用 内部方法 _createCompileDelegate,而后把返回值赋值给内部属性 _callAsync, 而且暴露外部属性 callAsync */
    this.callAsync = this._callAsync = this._createCompileDelegate("callAsync", "async"); // 用于调用函数的时候,保存钩子数组的变量
    this._x = undefined; } }

如上 类 Hook 是代码的初始化工做;对于上面的 _createCompileDelegate 这个方法,咱们先不用管,该方法在咱们的这个SyncHook 类暂时还用不上,由于这个是处理异步的操做,下面咱们会讲解到的。下面咱们须要看 注册事件 tap 方法.

代码以下:

tap(options, fn) { if(typeof options === "string") options = { name: options }; if(typeof options !== "object" || options === null) throw new Error("Invalid arguments to tap(options: Object, fn: function)"); options = Object.assign({ type: "sync", fn: fn }, options); if(typeof options.name !== "string" || options.name === "") throw new Error("Missing name for tap"); // 注册拦截器
  options = this._runRegisterInterceptors(options); // 插入钩子
  this._insert(options); }

如上该方法接收2个参数,第一个参数必须是一个字符串,若是它是字符串的话,那么 options = {name: options}, options 就返回了一个带name属性的对象了。而后使用 Object.assign()方法对对象合并,以下代码:options = Object.assign({ type: "sync", fn: fn }, options); 所以最后options就返回以下了:
options = { type: "sync", fn: fn, name: options };

而后就是调用以下方法,来注册一个拦截器;以下代码:

options = this._runRegisterInterceptors(options);

如今咱们来看下 _runRegisterInterceptors 代码以下所示:

_runRegisterInterceptors(options) { for(const interceptor of this.interceptors) { if(interceptor.register) { const newOptions = interceptor.register(options); if(newOptions !== undefined) options = newOptions; } } return options; }

如上_runRegisterInterceptors() 方法是注册拦截器的方法,该方法有一个options参数,该options的值是:

options = { type: "sync", fn: fn, name: '这是一个字符串' };

在该函数内部咱们遍历拦截器this.interceptors,而后拦截器 this.interceptors会有一个属性register,若是有该属性的话,就调用该属性来注册一个新的 newOptions 对象,若是 newOptions 对象 不等于 undefined的话,就把options = newOptions; 赋值给 options的 最后返回 options, 固然若是没有该拦截器的话,就直接返回该 options对象。

如上代码咱们知道,咱们在调用 tap()方法来注册事件以前,咱们就须要使用注册拦截器,来添加拦截器的,由于在调用_runRegisterInterceptors 方法时,它内部代码会遍历该拦截器,所以咱们就能够断定在咱们使用 tap 事件以前,咱们就须要使用 添加拦截器. 下面咱们来看看咱们添加拦截器的代码以下:

intercept(interceptor) { // 重置全部的调用方法
  this._resetCompilation(); // 保存拦截器到全局属性 interceptors内部,咱们使用 Object.assign方法复制了一份
  this.interceptors.push(Object.assign({}, interceptor)); /* 若是该拦截器有register属性的话,咱们就遍历全部的taps, 把他们做为参数调用拦截器的register,而且把返回的tap对象 (该tap对象指tap函数里面把fn和name这些信息组合起来的新对象)。而后赋值给 当前的某一项tap */
  if(interceptor.register) { for(let i = 0; i < this.taps.length; i++) this.taps[i] = interceptor.register(this.taps[i]); } }

下面咱们看下以下demo来继续理解下 intercept 方法的含义:以下demo所示:

const { SyncHook } = require('tapable'); const h1 = new SyncHook(['xxx']); h1.tap('A', function(args) { console.log('A', args); return 'b'; }); h1.tap('B', function() { console.log('b'); }); h1.tap('C', function() { console.log('c'); }); h1.tap('D', function() { console.log('d'); }); h1.intercept({ call: (...args) => { console.log(...args, '11111111'); }, register: (tap) => { console.log(tap, '222222'); return tap; }, loop: (...args) => { console.log(...args, '33333'); }, tap: (tap) => { console.log(tap, '444444'); } });

运行效果以下所示:

如上demo代码咱们能够看到,咱们在调用 tap 来注册咱们的事件的时候,咱们先会执行咱们的拦截器,也就是调用咱们的SyncHook类的实列对象 h1, 会调用 h1.intercept 方法的 register 函数,因此咱们注册了多少次,就使用拦截器拦截了多少次,而且返回了一个新的对象, 好比返回了 {type: "sync", fn: fn, name: 'A'} 这样的新对象。

咱们能够在返回看下咱们的 Hook.js 中的 tap(options, fn) {} 这个方法内部,该方法内部注册事件的时候,会先调用

options = this._runRegisterInterceptors(options);

这个函数代码,该函数代码的做用是注册拦截器,而后返回新的对象回来,以下代码所示:

_runRegisterInterceptors(options) { for(const interceptor of this.interceptors) { if(interceptor.register) { const newOptions = interceptor.register(options); if(newOptions !== undefined) options = newOptions; } } return options; }

如上咱们能够看到,它会返回了一个新对象,就是咱们上面打印出来的对象。该值会保存到咱们的 options 参数中,接着咱们继续执行 taps函数中的最后一句代码:

this._insert(options);

该函数的代码,就是把全部的事件对象保存到 this.taps 中。保存完成后,那么 this.taps 就有该值了,而后这个时候咱们就会调用咱们上面的 intercept 中的 register 这个函数。 下面咱们继续来看下 _insert(options) 中的代码吧,代码以下所示:

_insert(item) { // 重置资源,由于每个插件都会有一个新的 Compilation
  this._resetCompilation(); // 该item.before 是插件的名称
 let before; // 打印item
 console.log(item); /* before 能够是单个字符串插件的名称,也能够是一个字符串数组的插件 new Set 是ES6新增的,它的做用是去掉数组里面重复的值 */
  if(typeof item.before === "string") before = new Set([item.before]); else if(Array.isArray(item.before)) { before = new Set(item.before); } let stage = 0; if(typeof item.stage === "number") stage = item.stage; let i = this.taps.length; while(i > 0) { console.log('----', i); i--; const x = this.taps[i]; this.taps[i+1] = x; const xStage = x.stage || 0; if(before) { if(before.has(x.name)) { before.delete(x.name); continue; } if(before.size > 0) { continue; } } if(xStage > stage) { continue; } i++; break; } // 打印i的值
 console.log(i); this.taps[i] = item; }

如上代码使用while循环,遍历全部的taps的函数,而后会根据stage和before进行从新排序,stage的优先级低于before。以下demo,咱们也能够以下调用 tap, 好比tap是一个数组。以下所示:

const { SyncHook } = require('tapable'); const h1 = new SyncHook(['xxx']); h1.tap('A', function(args) { console.log('A', args); return 'b'; }); h1.tap('B', function() { console.log('b'); }); h1.tap('C', function() { console.log('c'); }); h1.tap({ name: 'F', before: 'D' }, function() { }); h1.tap({ name: 'E', before: 'C' }, function() { }); h1.tap('D', function() { console.log('d'); }); h1.intercept({ call: (...args) => { console.log(...args, '11111111'); }, register: (tap) => { console.log(tap, '222222'); return tap; }, loop: (...args) => { console.log(...args, '33333'); }, tap: (tap) => { console.log(tap, '444444'); } });

打印后的效果以下所示:

如上咱们能够看到咱们的 _insert(item); 方法中打印的console.log(item);项的值及打印console.log(i)的值及console.log('----', i); 咱们能够看下如上的 _insert(item)方法中的算法以下理解:

1. 首先在咱们的代码demo里面使用 tap 注册一个A事件,h1.tap('A', function(args) {}), 所以最后会把该函数返回的对象传递进来,对象为 {type: 'sync', fn: fn, name: 'A'}; 所以打印的 console.log(item); 就是该对象值,初始化第一步i的值为0. 由于 this.taps.length 的长度为0. 因此第一次不会进入 while循环内部,执行到最后咱们就把 console.log(i) 的值打印出来。

2. 当咱们使用 tap注册一个B事件的时候, h1.tap('B', function(args) {}); console.log(item); 就会打印出对象的值为:
{type: 'sync', fn:fn, name: 'B'}; 而后判断是否有before或state这个属性,若是没有的话,直接跳过,while (i > 0); 进入该while循环内部,打印 console.log('----', i); 所以会打印 '---- 1' 这样的,i--, 执行完后 i 的值会减一.  const x = this.taps[i]; 所以 x = this.taps[0] = {type: 'sync', fn: fn, name: 'A'}, this.taps[i+1] = x; 所以 this.taps[1] = {type: 'sync', fn: fn, name: 'A'}; 这样的。const xStage = x.stage || 0; 所以 xStage = 0; 由于咱们没有stage这个属性,也没有before属性,因此也不会进入上面的if语句,最后咱们会进行以下判断:

if(xStage > stage) { continue; }

也不会进入if语句,而后 i++; 所以此时 i = 1 了;所以会打印1. 此时咱们的 this.taps = [{type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}];

3. 当咱们使用tap注册一个C事件的时候,h1.tap('C', function() {}); 同理,打印出咱们的 console.log(item) 的值变为以下:
{type: "sync", fn: ƒ, name: "C"},同样也没有before和state属性,若是没有,直接跳过,接着就打印 ---2 而后此时 i=2了,最后咱们的 

this.taps = [{type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: "sync", fn: ƒ, name: "C"}];

4. 当咱们注册F事件的时候,如代码:h1.tap({name: 'F', before: 'D'}, function() {}), 此时 i = this.taps.length = 3; 所以第一次会打印 '---- 3';
 const x = this.taps[i]; const x = this.taps[2] = {type: "sync", fn: ƒ, name: "C"}; this.taps[i+1] = x; 所以 this.taps[3] = {type: "sync", fn: ƒ, name: "C"}; 而后会判断是否有before或state这个属性,咱们注册F事件的时候能够看到,它有before这个属性了,所以在内部i的值会从3依次循环,直接0为止,每次循环内部本身减小1. 所以最后咱们的i的值就变为0了,所以咱们的 this.tabs值就变成这样的了:

this.tabs = [{type: 'sync', name: 'F', before:'D', fn: fn}, {type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: "sync", fn: ƒ, name: "C"}];

5. 当咱们注册事件E的时候,如代码 h1.tap({name: 'E', before: 'C'}, function(){}); 此时咱们的i = 4了,所以会打印 '---- 4',
const x = this.taps[i]; const x = this.taps[3] = {type: "sync", fn: ƒ, name: "C"}; this.taps[i+1] = this.taps[4] = {type: "sync", fn: ƒ, name: "C"}; 而后会判断是否有before或state这个属性,咱们注册E事件的时候能够看到,它有before这个属性了,
后面逻辑依次类推....

上面代码分析的优势烦,咱们再来整理下思路,理解下 上面的算法:

1. 假如咱们注册了以下代码:

h1.tap('A', function(args) {}); h1.tap('B', function(args) {}); h1.tap('C', function(args) {}); h1.tap({name: 'F', before: 'D'}, function(args) {}); h1.tap({ name: 'E', before: 'C'}, function() {}); h1.tap('D', function() { console.log('d'); });

 如上注册了这么多函数,也就是说,咱们每次注册一个函数都会传递一个对象进来,

好比相似这样的:

{type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'C'}, {type: 'sync', fn: fn, name: 'F', before: 'D'}, {type: 'sync', fn: fn, name: 'E', before: 'C'}, {type: 'sync', fn: fn, name: 'D'}.

会依次调用咱们的 _insert(item) 这个函数,item的值就是上面咱们的依次循环的每一个对象的值。

2. 第一次传递 {type: 'sync', fn: fn, name: 'A'} 这个对象进来后,因为第一次咱们的 let i = this.taps.length; 的长度为0;所以就不会进行 代码的while内部循环,所以咱们的 this.taps = [{type: 'sync', fn: fn, name: 'A'}]; 这样的值。

3. 第二次传递 {type: 'sync', fn: fn, name: 'B'} 这个对象进来的时候,此次咱们的 let i = this.taps.length; 的长度为1了,所以
就会进入while循环,const x = this.taps[i]; const x = {type: 'sync', fn: fn, name: 'A'}; this.taps[i+1] = {type: 'sync', fn: fn, name: 'A'}; 所以这个时候 咱们的 this.taps = [{type: 'sync', fn: fn, name: 'A'},{type: 'sync', fn: fn, name: 'A'}] 了。因为该对象事件没有 stage 或 before 这个参数,所以最后执行 i++; 所以i的值变为1,最后一句代码:this.taps[i] = item; item的值就是咱们的 这个对象 {type: 'sync', fn: fn, name: 'B'}; 所以此时的 this.taps 的值,变为以下:

this.taps = [{type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}];

4. 第三次传递 {type: 'sync', fn: fn, name: 'C'} 这个对象进来的时候,和咱们的第三步骤同样,依次类推,所以最后咱们的 this.taps 的值变为以下:

this.taps = [{type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'C'}];

5. 第四次传递 {type: 'sync', fn: fn, name: 'F', before: 'D'} 这个对象进来的时候,此时咱们的 let i = this.taps.length = 3 了; 所以就会进入while循环,执行以下代码:i--; const x = this.taps[i]; this.taps[i+1] = x; 所以

const x = this.taps[2] = {type: 'sync', fn: fn, name: 'C'}; this.taps[i+1] = this.taps[3] = {type: 'sync', fn: fn, name: 'C'};

 所以此时咱们的 this.taps 对象的值变为以下:

this.taps = [ {type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'C'}, {type: 'sync', fn: fn, name: 'C'} ]; 

最后咱们就会执行下面的代码:

if(before) { if(before.has(x.name)) { before.delete(x.name); continue; } if(before.size > 0) { continue; } }

由于事件F有before这个参数,所以会进入if条件判断语句了,接着就判断 before.has(x.name); 判断该对象是否有 before 该值,好比咱们上面的的before为字符串 'D', 判断咱们以前保存的 this.taps 数组内部的每项对象的name属性是否有 'D' 这个字符串。若是有的话,就直接删除该对象。 因此一直没有找到 'D' 字符,所以会一直判断 if(before.size > 0) { continue; } 进行对内部i循环,所以i = 3;就循环了3次,依次是3, 2, 1 这样的,最后 i-- ;i = 0的时候,就不会进入while循环内部了,所以咱们在第一个位置会插入 F事件了,好比:

this.taps[0] = {type: 'sync', fn: fn, name: 'F', before: 'D'};

注意:上面再内部依次循环 3, 2, 1 的时候,咱们的this.taps的值数组会发生改变的,好比等于3的时候,咱们的数组是以下这个样子,由于执行了以下代码:

i--; const x = this.taps[i]; this.taps[i+1] = x;

i = 3 时,this.taps的值为 = [{type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'C'}, {type: 'sync', fn: fn, name: 'C'}];

i = 2 时,this.taps的值为 = [{type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'C'}];

i = 1时,this.taps的值为 = [{type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'C'}];

最后也就是咱们上面的 i = 0的时候,所以咱们会把 {type: 'sync', fn: fn, name: 'F', before: 'D'}; 对象值插入到咱们的 taps数组的第一个位置上了,所以 this.taps的值最终变为:[{type: 'sync', fn: fn, name: 'F', before: 'D'}, {type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'C'}];

6. 第五次传递 {type: 'sync', fn: fn, name: 'E', before: 'C'} 的时候,也是一样的道理,此时咱们的 let i = this.taps.length = 4 了; 所以就会进入while循环,执行以下代码:i--; const x = this.taps[i]; this.taps[i+1] = x; 所以咱们的 const x = this.taps[3] = {type: 'sync', fn: fn, name: 'C'}; this.taps[i+1] = this.taps[4] = {type: 'sync', fn: fn, name: 'C'};

在while内部循环,一样的道理也会执行以下代码:

if(before) { if(before.has(x.name)) { before.delete(x.name); continue; } if(before.size > 0) { continue; } } if(xStage > stage) { continue; } i++; break;

首先是有before这个参数的,所以会进入if语句内部,而后判断该before是否有该 x.name 属性吗?before的属性值为 'C'; 所以判断该有没有x.name 呢,咱们从上面知道咱们的 this.taps 的值为 = 

[ {type: 'sync', fn: fn, name: 'F', before: 'D'}, {type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'C'}, {type: 'sync', fn: fn, name: 'C'} ];

 从上面 i-- 可知,咱们此时i的值为3,所以咱们须要把该值插入到 this.taps[3] = {type: 'sync', fn: fn, name: 'E', before: 'C'} 了; 所以此时 this.taps的值就变为以下了;

this.taps = [ {type: 'sync', fn: fn, name: 'F', before: 'D'}, {type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'E', before: 'C'}, {type: 'sync', fn: fn, name: 'C'} ];

7. 第六次传递的D事件,也是一个意思,这里就再也不分析了,所以咱们的 this.taps的值最终变为以下:

this.taps = [ {type: 'sync', fn: fn, name: 'F', before: 'D'}, {type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'E', before: 'C'}, {type: 'sync', fn: fn, name: 'C'}, {type: 'sync', fn: fn, name: 'D'} ];

如上就是一个排序算法; 你们能够理解下。至于stage属性也是同样的,只是before属性的优先级相对于stage会更高。

理解 _createCompileDelegate() 函数代码

咱们再回到咱们的 Hook.js 中的构造函数内部有以下几句代码:

class Hook { constructor(args) { this.call = this._call = this._createCompileDelegate("call", "sync"); this.promise = this._promise = this._createCompileDelegate("promise", "promise"); this.callAsync = this._callAsync = this._createCompileDelegate("callAsync", "async"); } }

如上代码,咱们能够看到咱们的 this.call, this.promise, this.callAsync 都会调用 内部函数 _createCompileDelegate, 咱们来看看该内部函数的代码以下所示:

_createCompileDelegate(name, type) { const lazyCompileHook = (...args) => { this[name] = this._createCall(type); return this[name](...args); }; return lazyCompileHook; }

如上能够看到,_createCompileDelegate 函数接收2个参数,name 和 type,就是咱们上面调用该函数的时候传递进来的。而后在内部使用闭包的形式返回了 lazyCompileHook 函数,所以 this.call, this.promise, this.callAsync 都返回了该函数 lazyCompileHook 。

咱们再来看下如上demo,加上以下测试代码以下所示:

const { SyncHook } = require('tapable'); const h1 = new SyncHook(['xxx']); h1.tap('A', function(args) { console.log('A', args); return 'b'; }); h1.tap('B', function() { console.log('b'); }); h1.tap('C', function() { console.log('c'); }); h1.tap({ name: 'F', before: 'D' }, function() { }); h1.tap({ name: 'E', before: 'C' }, function() { }); h1.tap('D', function() { console.log('d'); }); h1.call(7777);

如上咱们打印的结果以下所示:

如上咱们调用call方法后,会所以执行 如上面的注册事件的回调函数,咱们再来看下_createCompileDelegate函数内部代码

_createCompileDelegate(name, type) { const lazyCompileHook = (...args) => { this[name] = this._createCall(type); return this[name](...args); }; return lazyCompileHook; }

该函数内部代码,返回了lazyCompileHook函数给咱们的call对象,而后当咱们的 this.call(7777)的时候就会调用lazyCompileHook函数,传递了一个参数,所以 ...args = 7777; 内部代码:

this[name] = this._createCall(type); 也就是说 这边的this对象指向了 SyncHook 的实列了,也就是咱们外面的实列 h1对象了,this['call'] = this._createCall('sync'); 咱们下面看下 _createCall 函数代码以下:

_createCall(type) { return this.compile({ taps: this.taps, interceptors: this.interceptors, args: this._args, type: type }); } compile(options) { throw new Error("Abstract: should be overriden"); }

如上代码,咱们是否是萌了?compile方法直接抛出一个对象?固然不是,咱们在 SyncHook 这个类中(其余的类也是同样),会对该方法进行重写的,咱们能够看下咱们的 SyncHook中的类代码,以下所示:

const HookCodeFactory = require("./HookCodeFactory"); const factory = new SyncHookCodeFactory(); class SyncHook extends Hook { tapAsync() { throw new Error("tapAsync is not supported on a SyncHook"); } tapPromise() { throw new Error("tapPromise is not supported on a SyncHook"); } compile(options) { factory.setup(this, options); return factory.create(options); } }

如上咱们能够看到 咱们的 compile 方法会进行重写该方法。如上的compile方法中的options的参数值就是咱们上面传递进来的,以下所示

options = { taps: this.taps, interceptors: this.interceptors, args: this._args, type: type }

如上看到,咱们引用了 HookCodeFactory 类进来,而且使用了 该类的实列 factory 中的 setUp()方法及 create()方法,咱们看下该 HookCodeFactory 类代码以下所示:

class HookCodeFactory { constructor(config) { this.config = config; this.options = undefined; } setup(instance, options) { instance._x = options.taps.map(t => t.fn); } create(options) { } }

如上 setup 中的参数 instance 就是调用该实列对象了,options的参数值就是咱们在 SyncHook.js 的参数以下值:

options = { taps: this.taps, interceptors: this.interceptors, args: this._args, type: type }

其中 this.taps 值就是咱们的上面的那个数组。 好比 :

this.taps = [ {type: 'sync', fn: fn, name: 'F', before: 'D'}, {type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'E', before: 'C'}, {type: 'sync', fn: fn, name: 'C'}, {type: 'sync', fn: fn, name: 'D'} ];

这样的, 而后每一个事件对象的实列都绑定到 instance._x = fn. 这里面的fn就是咱们this.taps数组里面遍历的fn函数。
每一个注册事件对应一个函数。会把该对应的事件函数绑定到 instance._x 上面来。咱们接下来再看下 咱们的 create()函数。

create()函数代码以下所示:

create(options) { this.init(options); switch(this.options.type) { case "sync": return new Function(this.args(), "\"use strict\";\n" + this.header() + this.content({ onError: err => `throw ${err};\n`, onResult: result => `return ${result};\n`, onDone: () => "", rethrowIfPossible: true })); case "async": return new Function(this.args({ after: "_callback" }), "\"use strict\";\n" + this.header() + this.content({ onError: err => `_callback(${err});\n`, onResult: result => `_callback(null, ${result});\n`, onDone: () => "_callback();\n" })); case "promise": let code = ""; code += "\"use strict\";\n"; code += "return new Promise((_resolve, _reject) => {\n"; code += "var _sync = true;\n"; code += this.header(); code += this.content({ onError: err => { let code = ""; code += "if(_sync)\n"; code += `_resolve(Promise.resolve().then(() => { throw ${err}; }));\n`; code += "else\n"; code += `_reject(${err});\n`; return code; }, onResult: result => `_resolve(${result});\n`, onDone: () => "_resolve();\n" }); code += "_sync = false;\n"; code += "});\n"; return new Function(this.args(), code); } } /** * @param {{ type: "sync" | "promise" | "async", taps: Array<Tap>, interceptors: Array<Interceptor> }} options */ init(options) { this.options = options; this._args = options.args.slice(); }

如上代码,其中咱们的 create(options) 函数中的参数 options 的值为以下:

options = { taps: this.taps, interceptors: this.interceptors, args: this._args, type: type }

而咱们的type值为 'sync', 所以会进入case语句中的第一个case,该create函数内部,判断三种类型的状况,分别为 'sync', 'async', 'promise'.

如上代码 options 对象中的参数:taps 是咱们的注册事件对象的数组,interceptors 是过滤器,目前是 []; 咱们的demo里面没有使用过滤器,固然咱们也可使用过滤器,args 参数值为 ['xxx']; 咱们初始化实列的时候 传递了该值;好比以下初始化该类代码:const h1 = new SyncHook(['xxx']); 而后咱们的type为 'sync' 了 。由于咱们 h1实列调用的是call这个方法。搞清楚了上面各个参数的含义,咱们接下来往下看。

在create()方法内部,咱们首先会调用 init() 方法,以下代码所示:this.init(options); 在init内部代码中,

init(options) { this.options = options; this._args = options.args.slice(); }

this.options = options, 保存了该对象的引用。this._args = options.args.slice(); 保存了该数组传递进来的参数。

如今就会直接 case 'sync' 的状况了,以下代码所示:

case "sync": return new Function(this.args(), "\"use strict\";\n" + this.header() + this.content({ onError: err => `throw ${err};\n`, onResult: result => `return ${result};\n`, onDone: () => "", rethrowIfPossible: true }));

就会依次调用 this.args(); this.header(); this.content() 方法; 在 new Function(); 中如何调用方法看以下代码来理解,以下图所示:

下面咱们来看下 header() 方法以下代码所示:

header() { let code = ""; // this.needContext() 判断数组this.taps的某一项是否有 context属性,任意一项有的话,就返回true
  if(this.needContext()) { // 若是为true的话,var _context = {};
    code += "var _context = {};\n"; } else { // 不然的话, var _context; 值为undefined
    code += "var _context;\n"; } /* 在setup()中,咱们把全部的tap对象都给到了 instance, 所以这里的 this._x 就是咱们以前说的 instance._x; */ code += "var _x = this._x;\n"; // 若是有拦截器的话,保存拦截器数组到局部变量 _interceptors 中,且数组保存到 _taps中。
  if(this.options.interceptors.length > 0) { code += "var _taps = this.taps;\n"; code += "var _interceptors = this.interceptors;\n"; } /* 若是有拦截器的话,遍历。 获取到某一个拦截器 const interceptor = this.options.interceptors[i]; 若是该拦截器有call这个方法的话,就拼接字符串。所以若是有过滤器的话,最终会拼接成以下字符串: "use strict"; function(options) { var _context; var _x = this._x; var _taps = this.taps; var _interceptors = this.interceptors; // 下面就是循环拦截器,若是有一个拦截器的话 _interceptors[0].call(options); } */
  for(let i = 0; i < this.options.interceptors.length; i++) { const interceptor = this.options.interceptors[i]; if(interceptor.call) { code += `${this.getInterceptor(i)}.call(${this.args({ before: interceptor.context ? "_context" : undefined })});\n`; } } return code; } needContext() { for(const tap of this.options.taps) if(tap.context) return true; return false; } getInterceptor(idx) { return `_interceptors[${idx}]`; }

首先咱们来看下 needContext() 函数,该函数遍历 this.options.taps;它是咱们传进来的对象。this.options.taps 值以下:

this.options.taps = [ {type: 'sync', fn: fn, name: 'F', before: 'D'}, {type: 'sync', fn: fn, name: 'A'}, {type: 'sync', fn: fn, name: 'B'}, {type: 'sync', fn: fn, name: 'E', before: 'C'}, {type: 'sync', fn: fn, name: 'C'}, {type: 'sync', fn: fn, name: 'D'} ];

若是该数组中的某一个对象有 context 属性的话(该数组中任意一项),不然都没有context属性的话,会返回false。

如上代码:

if(this.needContext()) { code += "var _context = {};\n"; } else { code += "var _context;\n"; } code += "var _x = this._x;\n";

若是 this.needContext() 为true的话,var _context = {}; 不然的话 var _context; 值为undefined; 所以若是this.needContext() 返回true的话,code的值变为以下所示:

若是this.needContext()方法返回false的话,就返回以下所示的值:

咱们如今再来看看 this.content() 方法,content()方法并不在咱们的HookCodeFactory类中,它是子类本身实现的,所以咱们到 SyncHook类中去看代码以下所示:

class SyncHookCodeFactory extends HookCodeFactory { content({ onError, onResult, onDone, rethrowIfPossible }) { return this.callTapsSeries({ onError: (i, err) => onError(err), onDone, rethrowIfPossible }); } }

咱们再结合 HookCodeFactory.js类中,看create()函数的代码:

case "sync": return new Function(this.args(), "\"use strict\";\n" + this.header() + this.content({ onError: err => `throw ${err};\n`, onResult: result => `return ${result};\n`, onDone: () => "", rethrowIfPossible: true }));

如上代码,咱们的content方法中传的参数为一个对象;

{ onError: err => `throw ${err};\n`, onResult: result => `return ${result};\n`, onDone: () => "", rethrowIfPossible: true }

所以上面的 SyncHookCodeFactory 类 继承了 HookCodeFactory 中对应的参数为:

onError =  err => `throw ${err};\n`; onResult = result => `return ${result};\n`; onDone = () => ""; rethrowIfPossible = true;

如上 onError, onResult, onDone 都是一个函数,而后返回不一样的值。最后咱们调用 callTapsSeries 方法来执行; 下面咱们来看下该 callTapsSeries 方法;方法在 HookCodeFactory 类中,代码以下所示:

callTapsSeries({ onError, onResult, onDone, rethrowIfPossible }) { if(this.options.taps.length === 0) return onDone(); const firstAsync = this.options.taps.findIndex(t => t.type !== "sync"); const next = i => { if(i >= this.options.taps.length) { return onDone(); } const done = () => next(i + 1); const doneBreak = (skipDone) => { if(skipDone) return ""; return onDone(); } return this.callTap(i, { onError: error => onError(i, error, done, doneBreak), onResult: onResult && ((result) => { return onResult(i, result, done, doneBreak); }), onDone: !onResult && (() => { return done(); }), rethrowIfPossible: rethrowIfPossible && (firstAsync < 0 || i < firstAsync) }); }; return next(0); }

如上代码:if(this.options.taps.length === 0) { return onDone(); } 的含义:若是 taps 处理完毕后或一个taps的长度都没有的话,就执行 onDone 方法,返回一个空字符串。

const firstAsync = this.options.taps.findIndex(t => t.type !== "sync"); 若是第一个异步的下标index. 经过t.type !== 'sync' 来判断,若是没有异步的话,就返回 -1; findIndex的使用方式以下所示:

下面咱们来看这个函数调用,以下next方法以下所示:

const next = i => { if(i >= this.options.taps.length) { return onDone(); } const done = () => next(i + 1); const doneBreak = (skipDone) => { if(skipDone) return ""; return onDone(); } return this.callTap(i, { onError: error => onError(i, error, done, doneBreak), onResult: onResult && ((result) => { return onResult(i, result, done, doneBreak); }), onDone: !onResult && (() => { return done(); }), rethrowIfPossible: rethrowIfPossible && (firstAsync < 0 || i < firstAsync) }); }; return next(0);

默认状况下,咱们看到 i = 0; 开始传递参数进去,若是 i 大于咱们的 注册事件函数的 this.taps的数组的话,就直接返回 咱们上面的 onDone()方法。若是不大于,就定义 done 函数,依次递归调用该next()函数,注意咱们这边的 done 函数目前尚未被执行到。只是定义了一个 done函数方法放在这里,接下来就是咱们的 doneBreak 函数了,它接收一个参数为 skipDone;若是有该参数的话,直接返回空字符串,不然的话,返回调用 onDone() 方法。最关键的一步在最后,最后咱们返回了 this.callTap 这个函数,也就是说,咱们的 callTapsSeries 函数方法的返回值决定于 callTap 这个方法的返回值。以前咱们定义了 done() 函数递归调用及 定义了 doneBreak 函数都是为 callTap 函数作准备的。callTap函数接收2个参数,第一个参数为 i 值,第二个参数为一个对象,该对象定义了 onError,onResult 及 onDone,rethrowIfPossible 函数。

callTap 函数变成以下:

this.callTap(i, { onError: function(error) { onError(i, error, done, doneBreak); }, onResult: onResult && function(result) { return onResult(i, result, done, doneBreak); }, onDone: !onResult && function() { return done(); }, rethrowIfPossible: rethrowIfPossible && (firstAsync < 0 || i < firstAsync) });

如上就是callTap函数的最终形式了,若是有onResult的话,就会返回一个匿名函数function, 而后咱们调用该函数onResult便可。
若是咱们没有 onResult 函数话,那么咱们能够调用 done函数,该done函数咱们上面定义了以下代码:const done = () => next(i + 1); 所以若是咱们没有传递onResult函数的话,它会依次循环咱们以前使用 this.taps保存的全部事件,而后依次循环该事件 对应的回调函数。就比如咱们下面的demo同样当咱们调用 call 方法后,它会依次调用该回调函数,而后输出信息出来,以下demo:

const { SyncHook } = require('tapable'); const h1 = new SyncHook(['xxx']); h1.tap('A', function(args) { console.log('A', args); return 'b'; }); h1.tap('B', function() { console.log('b'); }); h1.tap('C', function() { console.log('c'); }); h1.tap({ name: 'F', before: 'D' }, function() { }); h1.tap({ name: 'E', before: 'C' }, function() { }); h1.tap('D', function() { console.log('d'); }); h1.call(7777);

如上依次输出 A 7777 b c d

咱们再看看 rethrowIfPossible: rethrowIfPossible && (firstAsync < 0 || i < firstAsync)  rethrowIfPossible 默认返回true,所以会执行后面的语句,若是咱们的注册事件中有异步函数的话,那么咱们的 firstAsync 参数就会返回该异步函数的索引值,由于咱们上面的demo,注册事件没有异步函数,所以咱们的 firstAsync 返回的值是 -1; 所以 -1 < 0; 所以返回true;后面的 i < firstAsync; 看不看无所谓,由于这里使用了 || 这个语句符。固然若是咱们注册事件中有异步函数的话,那么咱们就会继续 判断 i < firstAsync 这个语句了。若是rethrowIfPossible 是false的话,那么当前的钩子函数的类型就不是 sync,多是Async或promise类型了。

下面咱们来看下 callTap 函数,代码以下所示:

/* tapIndex 是下标索引。 onError: onError(i, error, done, doneBreak); onResult:undefined, 由于上面调用的时候 没有 onResult 这个参数,因此返回undefined onDone: done(); 会递归调用咱们上面的 next() 函数。 rethrowIfPossible:默认为true. 若是为false的话,说明当前的钩子不是 sync,若是为true的话,说明当前的钩子函数是 Async 或 Promise */ callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) { let code = ""; let hasTapCached = false; // 遍历拦截器,若是有拦截器的话,若是有就执行拦截器的tap函数
  for(let i = 0; i < this.options.interceptors.length; i++) { const interceptor = this.options.interceptors[i]; if(interceptor.tap) { if(!hasTapCached) { /* 以下代码,咱们调用 this.getTap(tapIndex)方法后,会生成 `var _tap[0] = _tap[0]` 等这样的字符串。 生成完成后,咱们设置 hasTapCached 为true。若是有多个拦截器的话,咱们也会执行一次。 注意:咱们这边获取 _taps 对象的下标是使用咱们传进来的参数 tapIndex。在for循环中,咱们的tapIndex值不会改变的 。 */ code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`; hasTapCached = true; } /* 下面的代码返回的是:code += `_interceptors[0].tap(_tap0)`; 首先会判断该拦截器是否有 context 这个属性,若是有的话就获取 _context 这个属性,不然的话就空字符串。 */ code += `${this.getInterceptor(i)}.tap(${interceptor.context ? "_context, " : ""}_tap${tapIndex});\n`; } } /* 下面的代码返回了: code += `var _fn0 = _x[0]` */ code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`; // 获取 this.taps的索引,获取第一个或第n个
  const tap = this.options.taps[tapIndex]; // 判断类型,是不是 sync, Async 及 Promise 对象的
  /* rethrowIfPossible 默认为true,同步执行,若是有异步的话,rethrowIfPossible 返回false,就执行if语句代码, 所以代码 code += `var _hasError0 = false`; code += "try { \n" 这样的,若是是异步的话,由于要保证异步顺序的 问题,所以这边使用了 try catch 这样的语句,防止报错发生。 */
  switch(tap.type) { case "sync": if(!rethrowIfPossible) { code += `var _hasError${tapIndex} = false;\n`; code += "try {\n"; } /* 判断 onResult 是否为true仍是false, 若是为true的话,那么 code += `var _result0 = _fn0(options)` 若是为false的话,code += `_fn0(options)`; 这样的方法调用 */
      if(onResult) { code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({ before: tap.context ? "_context" : undefined })});\n`; } else { code += `_fn${tapIndex}(${this.args({ before: tap.context ? "_context" : undefined })});\n`; } // 把catch语句拼接上
      if(!rethrowIfPossible) { code += "} catch(_err) {\n"; code += `_hasError${tapIndex} = true;\n`; code += onError("_err"); code += "}\n"; code += `if(!_hasError${tapIndex}) {\n`; } // 有 onResult 的话,code += onResult(`_result0`); 就调用该方法执行。这边是字符串拼接。
      if(onResult) { code += onResult(`_result${tapIndex}`); } // 若是有 onDone() 方法的话,就开始递归调用。咱们以前有 next(i+1); 这样的递归。
      if(onDone) { code += onDone(); } if(!rethrowIfPossible) { code += "}\n"; } /* 所以若是咱们注册的是同步事件的话,那么咱们的最终代码就变成以下: var _tap[0] = _tap[0]; _interceptors[0].tap(_tap0); var _fn0 = _x[0]; _fn0(options); 若是咱们的this.taps 有多个同步事件的话,会依次类推... 所以会有以下这样的: var _tap[0] = _tap[0]; _interceptors[0].tap(_tap0); var _fn0 = _x[0]; _fn0(options); var _tap[1] = _tap[1]; _interceptors[1].tap(_tap1); var _fn1 = _x[1]; _fn0(options); ..... 依次类推 */
      break; case "async": let cbCode = ""; if(onResult) cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`; else cbCode += `_err${tapIndex} => {\n`; cbCode += `if(_err${tapIndex}) {\n`; cbCode += onError(`_err${tapIndex}`); cbCode += "} else {\n"; if(onResult) { cbCode += onResult(`_result${tapIndex}`); } if(onDone) { cbCode += onDone(); } cbCode += "}\n"; cbCode += "}"; code += `_fn${tapIndex}(${this.args({ before: tap.context ? "_context" : undefined, after: cbCode })});\n`; break; case "promise": code += `var _hasResult${tapIndex} = false;\n`; code += `_fn${tapIndex}(${this.args({ before: tap.context ? "_context" : undefined })}).then(_result${tapIndex} => {\n`; code += `_hasResult${tapIndex} = true;\n`; if(onResult) { code += onResult(`_result${tapIndex}`); } if(onDone) { code += onDone(); } code += `}, _err${tapIndex} => {\n`; code += `if(_hasResult${tapIndex}) throw _err${tapIndex};\n`; code += onError(`_err${tapIndex}`); code += "});\n"; break; } return code; } getTap(idx) { return `_taps[${idx}]`; } getInterceptor(idx) { return `_interceptors[${idx}]`; } getTapFn(idx) { return `_x[${idx}]`; }

所以咱们这边同步事件在 SyncHook 类中的 compile方法:

compile(options) { factory.setup(this, options); return factory.create(options); }

在返回代码以前,咱们仍是整理下整个思路吧,咱们首先在 Hook类中代码以下:

class Hook { _createCall(type) { return this.compile({ taps: this.taps, interceptors: this.interceptors, args: this._args, type: type }); } }

在咱们的子类 SyncHook中重写了 compile 该方法,代码以下:

compile(options) { factory.setup(this, options); return factory.create(options); }

所以咱们会调用 setup该方法,代码以下:

setup(instance, options) { instance._x = options.taps.map(t => t.fn); }

最后咱们会调用 factory.create(options); 这句代码,所以会调用 HookCodeFactory.js 代码中的 create()方法。
该方法判断了三种类型,分别为 sync, Async, promise 等。由于咱们这边都是同步事件,所以会调用 sync 这个case状况。
所以咱们最终代码返回变成以下:

"use strict"
function(options) { // 咱们首先执行 HookCodeFactory类中的 header() 方法生成代码
  var _context; var _x = this._x; // 若是咱们有拦截器的话,下面代码也会生成的,若是没有就忽略下面三句代码:
  var _taps = this.taps; var _interceptors = this.interceptors; /* 若是咱们只有一个拦截器的话,只会生成一个,若是咱们有多个的话,就会使用for循环生成多个 */ _interceptors[0].call(options); // 下面就是咱们的callTap函数返回的代码了
   var _tap[0] = _tap[0]; _interceptors[0].tap(_tap0); var _fn0 = _x[0]; _fn0(options); var _tap[1] = _tap[1]; _interceptors[1].tap(_tap1); var _fn1 = _x[1]; _fn0(options); ..... 依次类推 }

如上差很少就是一整个同步事件的流程了。至于其余的异步Async 和 promise,你们有空能够去折腾一下,里面的逻辑有点多。
按照上面的咱们思路也能够简单的折腾下了。咱们能够看到,咱们上面的同步事件的demo是如何被执行的,及它的回调函数是何时被执行的。固然里面还有不少参数判断,咱们能够去根据API文档或他们写的测试用例去理解下应该差很少了。

原文出处:https://www.cnblogs.com/tugenhua0707/p/11317557.html

相关文章
相关标签/搜索