<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
        問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
        當(dāng)前位置: 首頁 - 科技 - 知識百科 - 正文

        淺談Webpack核心模塊tapable解析

        來源:懂視網(wǎng) 責(zé)編:小采 時間:2020-11-27 22:07:53
        文檔

        淺談Webpack核心模塊tapable解析

        淺談Webpack核心模塊tapable解析:本文介紹了Webpack核心模塊tapable,分享給大家,具體如下: 前言 Webpack 是一個現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器,是對前端項目實現(xiàn)自動化和優(yōu)化必不可少的工具,Webpack 的 loader (加載器)和 plugin (插件)是由 Webpack
        推薦度:
        導(dǎo)讀淺談Webpack核心模塊tapable解析:本文介紹了Webpack核心模塊tapable,分享給大家,具體如下: 前言 Webpack 是一個現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器,是對前端項目實現(xiàn)自動化和優(yōu)化必不可少的工具,Webpack 的 loader (加載器)和 plugin (插件)是由 Webpack

        本文介紹了Webpack核心模塊tapable,分享給大家,具體如下:

        前言

        Webpack 是一個現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器,是對前端項目實現(xiàn)自動化和優(yōu)化必不可少的工具,Webpack 的 loader (加載器)和 plugin (插件)是由 Webpack 開發(fā)者和社區(qū)開發(fā)者共同貢獻(xiàn)的,而目前又沒有比較系統(tǒng)的開發(fā)文檔,想寫加載器和插件必須要懂 Webpack 的原理,即看懂 Webpack 的源碼, tapable 則是 Webpack 依賴的核心庫,可以說不懂 tapable 就看不懂 Webpack 源碼,所以本篇會對 tapable 提供的類進(jìn)行解析和模擬。

        tapable 介紹

        Webpack 本質(zhì)上是一種事件流的機(jī)制,它的工作流程就是將各個插件串聯(lián)起來,而實現(xiàn)這一切的核心就是 tapable ,Webpack 中最核心的,負(fù)責(zé)編譯的 Compiler 和負(fù)責(zé)創(chuàng)建 bundles 的 Compilation 都是 tapable 構(gòu)造函數(shù)的實例。

        打開 Webpack 4.0 的源碼中一定會看到下面這些以 Sync 、 Async 開頭,以 Hook 結(jié)尾的方法,這些都是 tapable 核心庫的類,為我們提供不同的事件流執(zhí)行機(jī)制,我們稱為 “鉤子”。

        // 引入 tapable 如下
        const {
         SyncHook,
         SyncBailHook,
         SyncWaterfallHook,
         SyncLoopHook,
         AsyncParallelHook,
         AsyncParallelBailHook,
         AsyncSeriesHook,
         AsyncSeriesBailHook,
         AsyncSeriesWaterfallHook
         } = require("tapable");
        
        

        上面的實現(xiàn)事件流機(jī)制的 “鉤子” 大方向可以分為兩個類別,“同步” 和 “異步”,“異步” 又分為兩個類別,“并行” 和 “串行”,而 “同步” 的鉤子都是串行的。

        Sync 類型的鉤子

        1、SyncHook

        SyncHook 為串行同步執(zhí)行,不關(guān)心事件處理函數(shù)的返回值,在觸發(fā)事件之后,會按照事件注冊的先后順序執(zhí)行所有的事件處理函數(shù)。

        // SyncHook 鉤子的使用
        const { SyncHook } = require("tapable");
        
        // 創(chuàng)建實例
        let 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));
        
        // 觸發(fā)事件,讓監(jiān)聽函數(shù)執(zhí)行
        syncHook.call("panda", 18);
        
        // 1 panda 18
        // 2 panda 18
        // 3 panda 18
        
        

        在 tapable 解構(gòu)的 SyncHook 是一個類,注冊事件需先創(chuàng)建實例,創(chuàng)建實例時支持傳入一個數(shù)組,數(shù)組內(nèi)存儲事件觸發(fā)時傳入的參數(shù),實例的 tap 方法用于注冊事件,支持傳入兩個參數(shù),第一個參數(shù)為事件名稱,在 Webpack 中一般用于存儲事件對應(yīng)的插件名稱(名字隨意,只是起到注釋作用), 第二個參數(shù)為事件處理函數(shù),函數(shù)參數(shù)為執(zhí)行 call 方法觸發(fā)事件時所傳入的參數(shù)的形參。

        // 模擬 SyncHook 類
        class SyncHook {
         constructor(args) {
         this.args = args;
         this.tasks = [];
         }
         tap(name, task) {
         this.tasks.push(task);
         }
         call(...args) {
         // 也可在參數(shù)不足時拋出異常
         if (args.length < this.args.length) throw new Error("參數(shù)不足");
        
         // 傳入?yún)?shù)嚴(yán)格對應(yīng)創(chuàng)建實例傳入數(shù)組中的規(guī)定的參數(shù),執(zhí)行時多余的參數(shù)為 undefined
         args = args.slice(0, this.args.length);
        
         // 依次執(zhí)行事件處理函數(shù)
         this.tasks.forEach(task => task(...args));
         }
        }
        
        

        tasks 數(shù)組用于存儲事件處理函數(shù), call 方法調(diào)用時傳入?yún)?shù)超過創(chuàng)建 SyncHook 實例傳入的數(shù)組長度時,多余參數(shù)可處理為 undefined ,也可在參數(shù)不足時拋出異常,不靈活,后面的例子中就不再這樣寫了。

        2、SyncBailHook

        SyncBailHook 同樣為串行同步執(zhí)行,如果事件處理函數(shù)執(zhí)行時有一個返回值不為空(即返回值為 undefined ),則跳過剩下未執(zhí)行的事件處理函數(shù)(如類的名字,意義在于保險)。

        // SyncBailHook 鉤子的使用
        const { SyncBailHook } = require("tapable");
        
        // 創(chuàng)建實例
        let 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));
        
        // 觸發(fā)事件,讓監(jiān)聽函數(shù)執(zhí)行
        syncBailHook.call("panda", 18);
        
        // 1 panda 18
        // 2 panda 18
        
        

        通過上面的用法可以看出, SyncHook 和 SyncBailHook 在邏輯上只是 call 方法不同,導(dǎo)致事件的執(zhí)行機(jī)制不同,對于后面其他的 “鉤子”,也是 call 的區(qū)別,接下來實現(xiàn) SyncBailHook 類。

        // 模擬 SyncBailHook 類
        class SyncBailHook {
         constructor(args) {
         this.args = args;
         this.tasks = [];
         }
         tap(name, task) {
         this.tasks.push(task);
         }
         call(...args) {
         // 傳入?yún)?shù)嚴(yán)格對應(yīng)創(chuàng)建實例傳入數(shù)組中的規(guī)定的參數(shù),執(zhí)行時多余的參數(shù)為 undefined
         args = args.slice(0, this.args.length);
        
         // 依次執(zhí)行事件處理函數(shù),如果返回值不為空,則停止向下執(zhí)行
         let i = 0, ret;
         do {
         ret = this.tasks[i++](...args);
         } while (!ret);
         }
        }
        
        

        在上面代碼的 call 方法中,我們設(shè)置返回值為 ret ,第一次執(zhí)行后沒有返回值則繼續(xù)循環(huán)執(zhí)行,如果有返回值則立即停止循環(huán),即實現(xiàn) “保險” 的功能。

        3、SyncWaterfallHook

        SyncWaterfallHook 為串行同步執(zhí)行,上一個事件處理函數(shù)的返回值作為參數(shù)傳遞給下一個事件處理函數(shù),依次類推,正因如此,只有第一個事件處理函數(shù)的參數(shù)可以通過 call 傳遞,而 call 的返回值為最后一個事件處理函數(shù)的返回值。

        // SyncWaterfallHook 鉤子的使用
        const { SyncWaterfallHook } = require("tapable");
        
        // 創(chuàng)建實例
        let syncWaterfallHook = new SyncWaterfallHook(["name", "age"]);
        
        // 注冊事件
        syncWaterfallHook.tap("1", (name, age) => {
         console.log("1", name, age);
         return "1";
        });
        
        syncWaterfallHook.tap("2", data => {
         console.log("2", data);
         return "2";
        });
        
        syncWaterfallHook.tap("3", data => {
         console.log("3", data);
         return "3"
        });
        
        // 觸發(fā)事件,讓監(jiān)聽函數(shù)執(zhí)行
        let ret = syncWaterfallHook.call("panda", 18);
        console.log("call", ret);
        
        // 1 panda 18
        // 2 1
        // 3 2
        // call 3
        
        

        SyncWaterfallHook 名稱中含有 “瀑布”,通過上面代碼可以看出 “瀑布” 形象生動的描繪了事件處理函數(shù)執(zhí)行的特點,與 SyncHook 和 SyncBailHook 的區(qū)別就在于事件處理函數(shù)返回結(jié)果的流動性,接下來看一下 SyncWaterfallHook 類的實現(xiàn)。

        // 模擬 SyncWaterfallHook 類
        class SyncWaterfallHook {
         constructor(args) {
         this.args = args;
         this.tasks = [];
         }
         tap(name, task) {
         this.tasks.push(task);
         }
         call(...args) {
         // 傳入?yún)?shù)嚴(yán)格對應(yīng)創(chuàng)建實例傳入數(shù)組中的規(guī)定的參數(shù),執(zhí)行時多余的參數(shù)為 undefined
         args = args.slice(0, this.args.length);
        
         // 依次執(zhí)行事件處理函數(shù),事件處理函數(shù)的返回值作為下一個事件處理函數(shù)的參數(shù)
         let [first, ...others] = this.tasks;
         return reduce((ret, task) => task(ret), first(...args));
         }
        }
        

        上面代碼中 call 的邏輯是將存儲事件處理函數(shù)的 tasks 拆成兩部分,分別為第一個事件處理函數(shù),和存儲其余事件處理函數(shù)的數(shù)組,使用 reduce 進(jìn)行歸并,將第一個事件處理函數(shù)執(zhí)行后的返回值作為歸并的初始值,依次調(diào)用其余事件處理函數(shù)并傳遞上一次歸并的返回值。

        4、SyncLoopHook

        SyncLoopHook 為串行同步執(zhí)行,事件處理函數(shù)返回 true 表示繼續(xù)循環(huán),即循環(huán)執(zhí)行當(dāng)前事件處理函數(shù),返回 undefined 表示結(jié)束循環(huán), SyncLoopHook 與 SyncBailHook 的循環(huán)不同, SyncBailHook 只決定是否繼續(xù)向下執(zhí)行后面的事件處理函數(shù),而 SyncLoopHook 的循環(huán)是指循環(huán)執(zhí)行每一個事件處理函數(shù),直到返回 undefined 為止,才會繼續(xù)向下執(zhí)行其他事件處理函數(shù),執(zhí)行機(jī)制同理。

        // SyncLoopHook 鉤子的使用
        const { SyncLoopHook } = require("tapable");
        
        // 創(chuàng)建實例
        let 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));
        
        // 觸發(fā)事件,讓監(jiān)聽函數(shù)執(zhí)行
        syncLoopHook.call("panda", 18);
        
        // 1 panda 18 0
        // 1 panda 18 1
        // 1 panda 18 2
        // 2 panda 18 0
        // 2 panda 18 1
        // 2 panda 18 2
        // 3 panda 18
        
        

        通過上面的執(zhí)行結(jié)果可以清楚的看到 SyncLoopHook 的執(zhí)行機(jī)制,但有一點需要注意,返回值必須嚴(yán)格是 true 才會觸發(fā)循環(huán),多次執(zhí)行當(dāng)前事件處理函數(shù),必須嚴(yán)格返回 undefined ,才會結(jié)束循環(huán),去執(zhí)行后面的事件處理函數(shù),如果事件處理函數(shù)的返回值不是 true 也不是 undefined ,則會死循環(huán)。

        在了解 SyncLoopHook 的執(zhí)行機(jī)制以后,我們接下來看看 SyncLoopHook 的 call 方法是如何實現(xiàn)的。

        // 模擬 SyncLoopHook 類
        class SyncLoopHook {
         constructor(args) {
         this.args = args;
         this.tasks = [];
         }
         tap(name, task) {
         this.tasks.push(task);
         }
         call(...args) {
         // 傳入?yún)?shù)嚴(yán)格對應(yīng)創(chuàng)建實例傳入數(shù)組中的規(guī)定的參數(shù),執(zhí)行時多余的參數(shù)為 undefined
         args = args.slice(0, this.args.length);
        
         // 依次執(zhí)行事件處理函數(shù),如果返回值為 true,則繼續(xù)執(zhí)行當(dāng)前事件處理函數(shù)
         // 直到返回 undefined,則繼續(xù)向下執(zhí)行其他事件處理函數(shù)
         this.tasks.forEach(task => {
         let ret;
         do {
         ret = this.task(...args);
         } while (ret === true || !(ret === undefined));
         });
         }
        }
        
        

        在上面代碼中可以看到 SyncLoopHook 類 call 方法的實現(xiàn)更像是 SyncHook 和 SyncBailHook 的 call 方法的結(jié)合版,外層循環(huán)整個 tasks 事件處理函數(shù)隊列,內(nèi)層通過返回值進(jìn)行循環(huán),控制每一個事件處理函數(shù)的執(zhí)行次數(shù)。

        注意:在 Sync 類型 “鉤子” 下執(zhí)行的插件都是順序執(zhí)行的,只能使用 tab 注冊。

        Async 類型的鉤子

        Async 類型可以使用 tap 、 tapSync 和 tapPromise 注冊不同類型的插件 “鉤子”,分別通過 call 、 callAsync 和 promise 方法調(diào)用,我們下面會針對 AsyncParallelHook 和 AsyncSeriesHook 的 async 和 promise 兩種方式分別介紹和模擬。

        1、AsyncParallelHook

        AsyncParallelHook 為異步并行執(zhí)行,通過 tapAsync 注冊的事件,通過 callAsync 觸發(fā),通過 tapPromise 注冊的事件,通過 promise 觸發(fā)(返回值可以調(diào)用 then 方法)。

        (1) tapAsync/callAsync

        callAsync 的最后一個參數(shù)為回調(diào)函數(shù),在所有事件處理函數(shù)執(zhí)行完畢后執(zhí)行。

        // AsyncParallelHook 鉤子:tapAsync/callAsync 的使用
        const { AsyncParallelHook } = require("tapable");
        
        // 創(chuàng)建實例
        let asyncParallelHook = new AsyncParallelHook(["name", "age"]);
        
        // 注冊事件
        console.time("time");
        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();
         console.timeEnd("time");
         }, 3000);
        });
        
        // 觸發(fā)事件,讓監(jiān)聽函數(shù)執(zhí)行
        asyncParallelHook.callAsync("panda", 18, () => {
         console.log("complete");
        });
        
        // 1 panda 18 2018-08-07T10:38:32.675Z
        // 2 panda 18 2018-08-07T10:38:33.674Z
        // 3 panda 18 2018-08-07T10:38:34.674Z
        // complete
        // time: 3005.060ms
        
        

        異步并行是指,事件處理函數(shù)內(nèi)三個定時器的異步操作最長時間為 3s ,而三個事件處理函數(shù)執(zhí)行完成總共用時接近 3s ,所以三個事件處理函數(shù)是幾乎同時執(zhí)行的,不需等待。

        所有 tabAsync 注冊的事件處理函數(shù)最后一個參數(shù)都為一個回調(diào)函數(shù) done ,每個事件處理函數(shù)在異步代碼執(zhí)行完畢后調(diào)用 done 函數(shù),則可以保證 callAsync 會在所有異步函數(shù)都執(zhí)行完畢后執(zhí)行,接下來看一看 callAsync 是如何實現(xiàn)的。

        // 模擬 AsyncParallelHook 類:tapAsync/callAsync
        class AsyncParallelHook {
         constructor(args) {
         this.args = args;
         this.tasks = [];
         }
         tabAsync(name, task) {
         this.tasks.push(task);
         }
         callAsync(...args) {
         // 先取出最后傳入的回調(diào)函數(shù)
         let finalCallback = args.pop();
        
         // 傳入?yún)?shù)嚴(yán)格對應(yīng)創(chuàng)建實例傳入數(shù)組中的規(guī)定的參數(shù),執(zhí)行時多余的參數(shù)為 undefined
         args = args.slice(0, this.args.length);
        
         // 定義一個 i 變量和 done 函數(shù),每次執(zhí)行檢測 i 值和隊列長度,決定是否執(zhí)行 callAsync 的回調(diào)函數(shù)
         let i = 0;
         let done = () => {
         if (++i === this.tasks.length) {
         finalCallback();
         }
         };
        
         // 依次執(zhí)行事件處理函數(shù)
         this.tasks.forEach(task => task(...args, done));
         }
        }
        
        

        在 callAsync 中,將最后一個參數(shù)(所有事件處理函數(shù)執(zhí)行完畢后執(zhí)行的回調(diào))取出,并定義 done 函數(shù),通過比較 i 和存儲事件處理函數(shù)的數(shù)組 tasks 的 length 來確定回調(diào)是否執(zhí)行,循環(huán)執(zhí)行每一個事件處理函數(shù)并將 done 作為最后一個參數(shù)傳入,所以每個事件處理函數(shù)內(nèi)部的異步操作完成時,執(zhí)行 done 就是為了檢測是不是該執(zhí)行 callAsync 的回調(diào),當(dāng)所有事件處理函數(shù)均執(zhí)行完畢滿足 done 函數(shù)內(nèi)部 i 和 length 相等的條件時,則調(diào)用 callAsync 的回調(diào)。

        (2) tapPromise/promise

        要使用 tapPromise 注冊事件,對事件處理函數(shù)有一個要求,必須返回一個 Promise 實例,而 promise 方法也返回一個 Promise 實例, callAsync 的回調(diào)函數(shù)在 promise 方法中用 then 的方式代替。

        // AsyncParallelHook 鉤子:tapPromise/promise 的使用
        const { AsyncParallelHook } = require("tapable");
        
        // 創(chuàng)建實例
        let asyncParallelHook = new AsyncParallelHook(["name", "age"]);
        
        // 注冊事件
        console.time("time");
        asyncParallelHook.tapPromise("1", (name, age) => {
         return new Promise((resolve, reject) => {
         settimeout(() => {
         console.log("1", name, age, new Date());
         resolve("1");
         }, 1000);
         });
        });
        
        asyncParallelHook.tapPromise("2", (name, age) => {
         return new Promise((resolve, reject) => {
         settimeout(() => {
         console.log("2", name, age, new Date());
         resolve("2");
         }, 2000);
         });
        });
        
        asyncParallelHook.tapPromise("3", (name, age) => {
         return new Promise((resolve, reject) => {
         settimeout(() => {
         console.log("3", name, age, new Date());
         resolve("3");
         console.timeEnd("time");
         }, 3000);
         });
        });
        
        // 觸發(fā)事件,讓監(jiān)聽函數(shù)執(zhí)行
        asyncParallelHook.promise("panda", 18).then(ret => {
         console.log(ret);
        });
        
        // 1 panda 18 2018-08-07T12:17:21.741Z
        // 2 panda 18 2018-08-07T12:17:22.736Z
        // 3 panda 18 2018-08-07T12:17:23.739Z
        // time: 3006.542ms
        // [ '1', '2', '3' ]
        
        

        上面每一個 tapPromise 注冊事件的事件處理函數(shù)都返回一個 Promise 實例,并將返回值傳入 resolve 方法,調(diào)用 promise 方法觸發(fā)事件時,如果所有事件處理函數(shù)返回的 Promise 實例結(jié)果都成功,會將結(jié)果存儲在數(shù)組中,并作為參數(shù)傳遞給 promise 的 then 方法中成功的回調(diào),如果有一個失敗就是將失敗的結(jié)果返回作為參數(shù)傳遞給失敗的回調(diào)。

        // 模擬 AsyncParallelHook 類 tapPromise/promise
        class AsyncParallelHook {
         constructor(args) {
         this.args = args;
         this.tasks = [];
         }
         tapPromise(name, task) {
         this.tasks.push(task);
         }
         promise(...args) {
         // 傳入?yún)?shù)嚴(yán)格對應(yīng)創(chuàng)建實例傳入數(shù)組中的規(guī)定的參數(shù),執(zhí)行時多余的參數(shù)為 undefined
         args = args.slice(0, this.args.length);
        
         // 將所有事件處理函數(shù)轉(zhuǎn)換成 Promise 實例,并發(fā)執(zhí)行所有的 Promise
         return Promise.all(this.tasks.map(task => task(...args)));
         }
        }
        
        

        其實根據(jù)上面對于 tapPromise 和 promise 使用的描述就可以猜到, promise 方法的邏輯是通過 Promise.all 來實現(xiàn)的。

        2、AsyncSeriesHook

        AsyncSeriesHook 為異步串行執(zhí)行,與 AsyncParallelHook 相同,通過 tapAsync 注冊的事件,通過 callAsync 觸發(fā),通過 tapPromise 注冊的事件,通過 promise 觸發(fā),可以調(diào)用 then 方法。

        (1) tapAsync/callAsync

        與 AsyncParallelHook 的 callAsync 方法類似, AsyncSeriesHook 的 callAsync 方法也是通過傳入回調(diào)函數(shù)的方式,在所有事件處理函數(shù)執(zhí)行完畢后執(zhí)行 callAsync 的回調(diào)函數(shù)。

        // AsyncSeriesHook 鉤子:tapAsync/callAsync 的使用
        const { AsyncSeriesHook } = require("tapable");
        
        // 創(chuàng)建實例
        let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);
        
        // 注冊事件
        console.time("time");
        asyncSeriesHook.tapAsync("1", (name, age, next) => {
         settimeout(() => {
         console.log("1", name, age, new Date());
         next();
         }, 1000);
        });
        
        asyncSeriesHook.tapAsync("2", (name, age, next) => {
         settimeout(() => {
         console.log("2", name, age, new Date());
         next();
         }, 2000);
        });
        
        asyncSeriesHook.tapAsync("3", (name, age, next) => {
         settimeout(() => {
         console.log("3", name, age, new Date());
         next();
         console.timeEnd("time");
         }, 3000);
        });
        
        // 觸發(fā)事件,讓監(jiān)聽函數(shù)執(zhí)行
        asyncSeriesHook.callAsync("panda", 18, () => {
         console.log("complete");
        });
        
        // 1 panda 18 2018-08-07T14:40:52.896Z
        // 2 panda 18 2018-08-07T14:40:54.901Z
        // 3 panda 18 2018-08-07T14:40:57.901Z
        // complete
        // time: 6008.790ms

        異步串行是指,事件處理函數(shù)內(nèi)三個定時器的異步執(zhí)行時間分別為 1s 、 2s 和 3s ,而三個事件處理函數(shù)執(zhí)行完總共用時接近 6s ,所以三個事件處理函數(shù)執(zhí)行是需要排隊的,必須一個一個執(zhí)行,當(dāng)前事件處理函數(shù)執(zhí)行完才能執(zhí)行下一個。

        AsyncSeriesHook 類的 tabAsync 方法注冊的事件處理函數(shù)參數(shù)中的 next 可以與 AsyncParallelHook 類中 tabAsync 方法參數(shù)的 done 進(jìn)行類比,同為回調(diào)函數(shù),不同點在于 AsyncSeriesHook 與 AsyncParallelHook 的 callAsync 方法的 “并行” 和 “串行” 的實現(xiàn)方式。

        // 模擬 AsyncSeriesHook 類:tapAsync/callAsync
        class AsyncSeriesHook {
         constructor(args) {
         this.args = args;
         this.tasks = [];
         }
         tabAsync(name, task) {
         this.tasks.push(task);
         }
         callAsync(...args) {
         // 先取出最后傳入的回調(diào)函數(shù)
         let finalCallback = args.pop();
        
         // 傳入?yún)?shù)嚴(yán)格對應(yīng)創(chuàng)建實例傳入數(shù)組中的規(guī)定的參數(shù),執(zhí)行時多余的參數(shù)為 undefined
         args = args.slice(0, this.args.length);
        
         // 定義一個 i 變量和 next 函數(shù),每次取出一個事件處理函數(shù)執(zhí)行,并維護(hù) i 的值
         // 直到所有事件處理函數(shù)都執(zhí)行完,調(diào)用 callAsync 的回調(diào)
         // 如果事件處理函數(shù)中沒有調(diào)用 next,則無法繼續(xù)
         let i = 0;
         let next = () => {
         let task = this.tasks[i++];
         task ? task(...args, next) : finalCallback();
         };
         next();
         }
        }
        
        

        AsyncParallelHook 是通過循環(huán)依次執(zhí)行了所有的事件處理函數(shù), done 方法只為了檢測是否已經(jīng)滿足條件執(zhí)行 callAsync 的回調(diào),如果中間某個事件處理函數(shù)沒有調(diào)用 done ,只是不會調(diào)用 callAsync 的回調(diào),但是所有的事件處理函數(shù)都執(zhí)行了。

        而 AsyncSeriesHook 的 next 執(zhí)行機(jī)制更像 Express 和 Koa 中的中間件,在注冊事件的回調(diào)中如果不調(diào)用 next ,則在觸發(fā)事件時會在沒有調(diào)用 next 的事件處理函數(shù)的位置 “卡死”,即不會繼續(xù)執(zhí)行后面的事件處理函數(shù),只有都調(diào)用 next 才能繼續(xù),而最后一個事件處理函數(shù)中調(diào)用 next 決定是否調(diào)用 callAsync 的回調(diào)。

        (2) tapPromise/promise

        與 AsyncParallelHook 類似, tapPromise 注冊事件的事件處理函數(shù)需要返回一個 Promise 實例, promise 方法最后也返回一個 Promise 實例。

        // AsyncSeriesHook 鉤子:tapPromise/promise 的使用
        const { AsyncSeriesHook } = require("tapable");
        
        // 創(chuàng)建實例
        let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);
        
        // 注冊事件
        console.time("time");
        asyncSeriesHook.tapPromise("1", (name, age) => {
         return new Promise((resolve, reject) => {
         settimeout(() => {
         console.log("1", name, age, new Date());
         resolve("1");
         }, 1000);
         });
        });
        
        asyncSeriesHook.tapPromise("2", (name, age) => {
         return new Promise((resolve, reject) => {
         settimeout(() => {
         console.log("2", name, age, new Date());
         resolve("2");
         }, 2000);
         });
        });
        
        asyncParallelHook.tapPromise("3", (name, age) => {
         return new Promise((resolve, reject) => {
         settimeout(() => {
         console.log("3", name, age, new Date());
         resolve("3");
         console.timeEnd("time");
         }, 3000);
         });
        });
        
        // 觸發(fā)事件,讓監(jiān)聽函數(shù)執(zhí)行
        asyncSeriesHook.promise("panda", 18).then(ret => {
         console.log(ret);
        });
        
        // 1 panda 18 2018-08-07T14:45:52.896Z
        // 2 panda 18 2018-08-07T14:45:54.901Z
        // 3 panda 18 2018-08-07T14:45:57.901Z
        // time: 6014.291ms
        // [ '1', '2', '3' ]
        
        

        分析上面的執(zhí)行過程,所有的事件處理函數(shù)都返回了 Promise 的實例,如果想實現(xiàn) “串行”,則需要讓每一個返回的 Promise 實例都調(diào)用 then ,并在 then 中執(zhí)行下一個事件處理函數(shù),這樣就保證了只有上一個事件處理函數(shù)執(zhí)行完后才會執(zhí)行下一個。

        // 模擬 AsyncSeriesHook 類 tapPromise/promise
        class AsyncSeriesHook {
         constructor(args) {
         this.args = args;
         this.tasks = [];
         }
         tapPromise(name, task) {
         this.tasks.push(task);
         }
         promise(...args) {
         // 傳入?yún)?shù)嚴(yán)格對應(yīng)創(chuàng)建實例傳入數(shù)組中的規(guī)定的參數(shù),執(zhí)行時多余的參數(shù)為 undefined
         args = args.slice(0, this.args.length);
        
         // 將每個事件處理函數(shù)執(zhí)行并調(diào)用返回 Promise 實例的 then 方法
         // 讓下一個事件處理函數(shù)在 then 方法成功的回調(diào)中執(zhí)行
         let [first, ...others] = this.tasks;
         return others.reduce((promise, task) => {
         return promise.then(() => task(...args));
         }, first(...args));
         }
        }
        
        

        上面代碼中的 “串行” 是使用 reduce 歸并來實現(xiàn)的,首先將存儲所有事件處理函數(shù)的數(shù)組 tasks 解構(gòu)成兩部分,第一個事件處理函數(shù)和存儲其他事件處理函數(shù)的數(shù)組 others ,對 others 進(jìn)行歸并,將第一個事件處理函數(shù)執(zhí)行后返回的 Promise 實例作為歸并的初始值,這樣在歸并的過程中上一個值始終是上一個事件處理函數(shù)返回的 Promise 實例,可以直接調(diào)用 then 方法,并在 then 的回調(diào)中執(zhí)行下一個事件處理函數(shù),直到歸并完成,將 reduce 最后返回的 Promise 實例作為 promise 方法的返回值,則實現(xiàn) promise 方法執(zhí)行后繼續(xù)調(diào)用 then 來實現(xiàn)后續(xù)邏輯。

        對其他異步鉤子補(bǔ)充

        在上面 Async 異步類型的 “鉤子中”,我們只著重介紹了 “串行” 和 “并行”( AsyncParallelHook 和 AsyncSeriesHook )以及回調(diào)和 Promise 的兩種注冊和觸發(fā)事件的方式,還有一些其他的具有一定特點的異步 “鉤子” 我們并沒有進(jìn)行分析,因為他們的機(jī)制與同步對應(yīng)的 “鉤子” 非常的相似。

        AsyncParallelBailHook 和 AsyncSeriesBailHook 分別為異步 “并行” 和 “串行” 執(zhí)行的 “鉤子”,返回值不為 undefined ,即有返回值,則立即停止向下執(zhí)行其他事件處理函數(shù),實現(xiàn)邏輯可結(jié)合 AsyncParallelHook 、 AsyncSeriesHook 和 SyncBailHook 。

        AsyncSeriesWaterfallHook 為異步 “串行” 執(zhí)行的 “鉤子”,上一個事件處理函數(shù)的返回值作為參數(shù)傳遞給下一個事件處理函數(shù),實現(xiàn)邏輯可結(jié)合 AsyncSeriesHook 和 SyncWaterfallHook 。

        總結(jié)

        在 tapable 源碼中,注冊事件的方法 tab 、 tapSync 、 tapPromise 和觸發(fā)事件的方法 call 、 callAsync 、 promise 都是通過 compile 方法快速編譯出來的,我們本文中這些方法的實現(xiàn)只是遵照了 tapable 庫這些 “鉤子” 的事件處理機(jī)制進(jìn)行了模擬,以方便我們了解 tapable ,為學(xué)習(xí) Webpack 原理做了一個鋪墊,在 Webpack 中,這些 “鉤子” 的真正作用就是將通過配置文件讀取的插件與插件、加載器與加載器之間進(jìn)行連接,“并行” 或 “串行” 執(zhí)行,相信在我們對 tapable 中這些 “鉤子” 的事件機(jī)制有所了解之后,再重新學(xué)習(xí) Webpack 的源碼應(yīng)該會有所頭緒。

        聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

        文檔

        淺談Webpack核心模塊tapable解析

        淺談Webpack核心模塊tapable解析:本文介紹了Webpack核心模塊tapable,分享給大家,具體如下: 前言 Webpack 是一個現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器,是對前端項目實現(xiàn)自動化和優(yōu)化必不可少的工具,Webpack 的 loader (加載器)和 plugin (插件)是由 Webpack
        推薦度:
        標(biāo)簽: 解析 核心 webpack
        • 熱門焦點

        最新推薦

        猜你喜歡

        熱門推薦

        專題
        Top
        主站蜘蛛池模板: 少妇亚洲免费精品| 亚洲国产精品福利片在线观看| 亚洲国产中文在线视频| 99re6热视频精品免费观看| 亚洲中文字幕久久精品无码喷水| 美女免费视频一区二区| 天堂亚洲免费视频| 精品亚洲国产成AV人片传媒| 久久免费区一区二区三波多野| 久久亚洲精品视频| 在线毛片片免费观看| 亚洲国产人成网站在线电影动漫 | 亚洲an日韩专区在线| 国产91免费在线观看| 亚洲AV一二三区成人影片| 成人最新午夜免费视频| 国产精品亚洲综合久久| 日批视频网址免费观看| 日本亚洲成高清一区二区三区| 无码囯产精品一区二区免费| 亚洲系列中文字幕| 日韩高清在线高清免费| 亚洲综合一区二区精品导航| 亚洲一区二区三区免费在线观看| 亚洲激情视频图片| 亚洲日韩中文在线精品第一 | 亚洲精品老司机在线观看| 国产精品视频全国免费观看| 精品国产综合成人亚洲区| 日韩精品内射视频免费观看| 国产精品亚洲片夜色在线| 亚洲日韩涩涩成人午夜私人影院| 久久久久久毛片免费看| 亚洲欧洲第一a在线观看| 歪歪漫画在线观看官网免费阅读| 国产天堂亚洲精品| 亚洲久本草在线中文字幕| 国产精品视频永久免费播放| 国产亚洲精品仙踪林在线播放| 亚洲成a人片在线观看无码专区| 日韩欧毛片免费视频|