webpack4核心模塊tapable源碼解析

閱讀目錄php

webpack打包是一種事件流的機制,它的原理是將各個插件串聯起來,那麼實現這一切的核心就是咱們要講解的tapable. 而且在webpack中負責編譯的Compiler和負責建立bundles的Compilation都是tapable構造函數的實列。node

WebPack的loader(加載器)和plugin(插件)是由Webpack開發者和社區開發者共同貢獻的。若是咱們想寫加載器和插件的話,咱們須要看懂Webpack的基本原理,也就是說要看懂Webpack源碼,咱們今天要講解的 tapable 則是webpack依賴的核心庫。所以在看懂webpack以前,咱們先要把 tapable 這個代碼看懂。webpack

我這邊講解的是基於 "webpack": "^4.16.1",這個版本的包的。安裝該webpack以後,該webpack會自帶 tapable 包。在tapable包下它是由以下js文件組成的。git

|---- 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

以下圖所示:github

Tapable的本質是能控制一系列註冊事件之間的執行流的機制。如上圖咱們能夠看到,都是以Sync, Async 及 Hook結尾的方法,他們爲咱們提供了不一樣的事件流執行機制,咱們能夠把它叫作 "鉤子"。那麼這些鉤子能夠分爲2個類別,即 "同步" 和 "異步", 異步又分爲兩個類別,"並行" 仍是 "串行",同步的鉤子它只有 "串行"。web

以下圖所示:算法

下面我會把全部的測試demo放在咱們的項目結構下的 public/js/main.js 文件代碼內,咱們能夠執行便可看到效果。下面是咱們項目中的目錄基本結構(很簡單,無非就是一個很簡單的運行本地demo的框架):
github中demo框架 json

能夠把該框架下載到本地來,下面的demo可使用該框架來測試代碼了。數組

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

一:理解Sync類型的鉤子promise

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文檔或他們寫的測試用例去理解下應該差很少了。

相關文章
相關標籤/搜索