<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          webpack核心模塊tapable源碼解析

          共 2064字,需瀏覽 5分鐘

           ·

          2021-11-30 17:49

          上一篇文章我寫了tapable的基本用法,我們知道他是一個(gè)增強(qiáng)版版的發(fā)布訂閱模式,本文想來(lái)學(xué)習(xí)下他的源碼。tapable的源碼我讀了一下,發(fā)現(xiàn)他的抽象程度比較高,直接扎進(jìn)去反而會(huì)讓人云里霧里的,所以本文會(huì)從最簡(jiǎn)單的SyncHook發(fā)布訂閱模式入手,再一步一步抽象,慢慢變成他源碼的樣子。

          本文可運(yùn)行示例代碼已經(jīng)上傳GitHub,大家拿下來(lái)一邊玩一邊看文章效果更佳:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Engineering/tapable-source-code

          SyncHook的基本實(shí)現(xiàn)

          上一篇文章已經(jīng)講過SyncHook的用法了,我這里就不再展開了,他使用的例子就是這樣子:

          const?{?SyncHook?}?=?require("tapable");

          //?實(shí)例化一個(gè)加速的hook
          const?accelerate?=?new?SyncHook(["newSpeed"]);

          //?注冊(cè)第一個(gè)回調(diào),加速時(shí)記錄下當(dāng)前速度
          accelerate.tap("LoggerPlugin",?(newSpeed)?=>
          ??console.log("LoggerPlugin",?`加速到${newSpeed}`)
          );

          //?再注冊(cè)一個(gè)回調(diào),用來(lái)檢測(cè)是否超速
          accelerate.tap("OverspeedPlugin",?(newSpeed)?=>?{
          ??if?(newSpeed?>?120)?{
          ????console.log("OverspeedPlugin",?"您已超速!!");
          ??}
          });

          //?觸發(fā)一下加速事件,看看效果吧
          accelerate.call(500);

          其實(shí)這種用法就是一個(gè)最基本的發(fā)布訂閱模式,我之前講發(fā)布訂閱模式的文章講過,我們可以仿照那個(gè)很快實(shí)現(xiàn)一個(gè)SyncHook

          class?SyncHook?{
          ????constructor(args?=?[])?{
          ????????this._args?=?args;???????//?接收的參數(shù)存下來(lái)
          ????????this.taps?=?[];??????????//?一個(gè)存回調(diào)的數(shù)組
          ????}

          ????//?tap實(shí)例方法用來(lái)注冊(cè)回調(diào)
          ????tap(name,?fn)?{
          ????????//?邏輯很簡(jiǎn)單,直接保存下傳入的回調(diào)參數(shù)就行
          ????????this.taps.push(fn);
          ????}

          ????//?call實(shí)例方法用來(lái)觸發(fā)事件,執(zhí)行所有回調(diào)
          ????call(...args)?{
          ????????//?邏輯也很簡(jiǎn)單,將注冊(cè)的回調(diào)一個(gè)一個(gè)拿出來(lái)執(zhí)行就行
          ????????const?tapsLength?=?this.taps.length;
          ????????for(let?i?=?0;?i?????????????const?fn?=?this.taps[i];
          ????????????fn(...args);
          ????????}
          ????}
          }

          這段代碼非常簡(jiǎn)單,是一個(gè)最基礎(chǔ)的發(fā)布訂閱模式,使用方法跟上面是一樣的,將SyncHooktapable導(dǎo)出改為使用我們自己的:

          //?const?{?SyncHook?}?=?require("tapable");
          const?{?SyncHook?}?=?require("./SyncHook");

          運(yùn)行效果是一樣的:

          709e93b8d55f94297487f0fcfa09c6cd.webpimage-20210323153234354

          注意: 我們構(gòu)造函數(shù)里面?zhèn)魅氲?code style="font-size:14px;color:rgb(30,107,184);background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;">args并沒有用上,tapable主要是用它來(lái)動(dòng)態(tài)生成call的函數(shù)體的,在后面講代碼工廠的時(shí)候會(huì)看到。

          SyncBailHook的基本實(shí)現(xiàn)

          再來(lái)一個(gè)SyncBailHook的基本實(shí)現(xiàn)吧,SyncBailHook的作用是當(dāng)前一個(gè)回調(diào)返回不為undefined的值的時(shí)候,阻止后面的回調(diào)執(zhí)行。基本使用是這樣的:

          const?{?SyncBailHook?}?=?require("tapable");????//?使用的是SyncBailHook

          const?accelerate?=?new?SyncBailHook(["newSpeed"]);

          accelerate.tap("LoggerPlugin",?(newSpeed)?=>
          ??console.log("LoggerPlugin",?`加速到${newSpeed}`)
          );

          //?再注冊(cè)一個(gè)回調(diào),用來(lái)檢測(cè)是否超速
          //?如果超速就返回一個(gè)錯(cuò)誤
          accelerate.tap("OverspeedPlugin",?(newSpeed)?=>?{
          ??if?(newSpeed?>?120)?{
          ????console.log("OverspeedPlugin",?"您已超速!!");

          ????return?new?Error('您已超速!!');
          ??}
          });

          //?由于上一個(gè)回調(diào)返回了一個(gè)不為undefined的值
          //?這個(gè)回調(diào)不會(huì)再運(yùn)行了
          accelerate.tap("DamagePlugin",?(newSpeed)?=>?{
          ??if?(newSpeed?>?300)?{
          ????console.log("DamagePlugin",?"速度實(shí)在太快,車子快散架了。。。");
          ??}
          });

          accelerate.call(500);

          他的實(shí)現(xiàn)跟上面的SyncHook也非常像,只是call在執(zhí)行的時(shí)候不一樣而已,SyncBailHook需要檢測(cè)每個(gè)回調(diào)的返回值,如果不為undefined就終止執(zhí)行后面的回調(diào),所以代碼實(shí)現(xiàn)如下:

          class?SyncBailHook?{
          ????constructor(args?=?[])?{
          ????????this._args?=?args;???????
          ????????this.taps?=?[];??????????
          ????}

          ????tap(name,?fn)?{
          ????????this.taps.push(fn);
          ????}

          ????//?其他代碼跟SyncHook是一樣的,就是call的實(shí)現(xiàn)不一樣
          ????//?需要檢測(cè)每個(gè)返回值,如果不為undefined就終止執(zhí)行
          ????call(...args)?{
          ????????const?tapsLength?=?this.taps.length;
          ????????for(let?i?=?0;?i?????????????const?fn?=?this.taps[i];
          ????????????const?res?=?fn(...args);

          ????????????if(?res?!==?undefined)?return?res;
          ????????}
          ????}
          }

          然后改下SyncBailHook從我們自己的引入就行:

          //?const?{?SyncBailHook?}?=?require("tapable");?
          const?{?SyncBailHook?}?=?require("./SyncBailHook");?

          運(yùn)行效果是一樣的:

          cc5854eb8621b1635419504d31e86f80.webpimage-20210323155857678

          抽象重復(fù)代碼

          現(xiàn)在我們只實(shí)現(xiàn)了SyncHookSyncBailHook兩個(gè)Hook而已,上一篇講用法的文章里面總共有9個(gè)Hook,如果每個(gè)Hook都像前面這樣實(shí)現(xiàn)也是可以的。但是我們?cè)僮屑?xì)看下SyncHookSyncBailHook兩個(gè)類的代碼,發(fā)現(xiàn)他們除了call的實(shí)現(xiàn)不一樣,其他代碼一模一樣,所以作為一個(gè)有追求的工程師,我們可以把這部分重復(fù)的代碼提出來(lái)作為一個(gè)基類:Hook類。

          Hook類需要包含一些公共的代碼,call這種不一樣的部分由各個(gè)子類自己實(shí)現(xiàn)。所以Hook類就長(zhǎng)這樣:

          const?CALL_DELEGATE?=?function(...args)?{
          ?this.call?=?this._createCall();
          ?return?this.call(...args);
          };

          //?Hook是SyncHook和SyncBailHook的基類
          //?大體結(jié)構(gòu)是一樣的,不一樣的地方是call
          //?不同子類的call是不一樣的
          //?tapable的Hook基類提供了一個(gè)抽象接口compile來(lái)動(dòng)態(tài)生成call函數(shù)
          class?Hook?{
          ????constructor(args?=?[])?{
          ????????this._args?=?args;???????
          ????????this.taps?=?[];??????????

          ????????//?基類的call初始化為CALL_DELEGATE
          ????????//?為什么這里需要這樣一個(gè)代理,而不是直接this.call?=?_createCall()
          ????????//?等我們后面子類實(shí)現(xiàn)了再一起講
          ????????this.call?=?CALL_DELEGATE;
          ????}

          ????//?一個(gè)抽象接口compile
          ????//?由子類實(shí)現(xiàn),基類compile不能直接調(diào)用
          ????compile(options)?{
          ??????throw?new?Error("Abstract:?should?be?overridden");
          ????}

          ????tap(name,?fn)?{
          ????????this.taps.push(fn);
          ????}

          ????//?_createCall調(diào)用子類實(shí)現(xiàn)的compile來(lái)生成call方法
          ????_createCall()?{
          ??????return?this.compile({
          ????????taps:?this.taps,
          ????????args:?this._args,
          ??????});
          ?}
          }

          官方對(duì)應(yīng)的源碼看這里:https://github.com/webpack/tapable/blob/master/lib/Hook.js

          子類SyncHook實(shí)現(xiàn)

          現(xiàn)在有了Hook基類,我們的SyncHook就需要繼承這個(gè)基類重寫,tapable在這里繼承的時(shí)候并沒有使用class extends,而是手動(dòng)繼承的:

          const?Hook?=?require('./Hook');

          function?SyncHook(args?=?[])?{
          ????//?先手動(dòng)繼承Hook
          ???const?hook?=?new?Hook(args);
          ????hook.constructor?=?SyncHook;

          ????//?然后實(shí)現(xiàn)自己的compile函數(shù)
          ????//?compile的作用應(yīng)該是創(chuàng)建一個(gè)call函數(shù)并返回
          ??hook.compile?=?function(options)?{
          ????????//?這里call函數(shù)的實(shí)現(xiàn)跟前面實(shí)現(xiàn)是一樣的
          ????????const?{?taps?}?=?options;
          ????????const?call?=?function(...args)?{
          ????????????const?tapsLength?=?taps.length;
          ????????????for(let?i?=?0;?i?????????????????const?fn?=?this.taps[i];
          ????????????????fn(...args);
          ????????????}
          ????????}

          ????????return?call;
          ????};
          ????
          ?return?hook;
          }

          SyncHook.prototype?=?null;

          注意:我們?cè)诨?code style="font-size:14px;color:rgb(30,107,184);background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;">Hook構(gòu)造函數(shù)中初始化this.callCALL_DELEGATE這個(gè)函數(shù),這是有原因的,最主要的原因是確保this的正確指向。思考一下假如我們不用CALL_DELEGATE,而是直接this.call = this._createCall()會(huì)發(fā)生什么?我們來(lái)分析下這個(gè)執(zhí)行流程:

          1. 用戶使用時(shí),肯定是使用new SyncHook(),這時(shí)候會(huì)執(zhí)行const hook = new Hook(args);
          2. new Hook(args)會(huì)去執(zhí)行Hook的構(gòu)造函數(shù),也就是會(huì)運(yùn)行this.call = this._createCall()
          3. 這時(shí)候的this指向的是基類Hook的實(shí)例,this._createCall()會(huì)調(diào)用基類的this.compile()
          4. 由于基類的complie函數(shù)是一個(gè)抽象接口,直接調(diào)用會(huì)報(bào)錯(cuò)Abstract: should be overridden

          那我們采用this.call = CALL_DELEGATE是怎么解決這個(gè)問題的呢

          1. 采用this.call = CALL_DELEGATE后,基類Hook上的call就只是被賦值為一個(gè)代理函數(shù)而已,這個(gè)函數(shù)不會(huì)立馬調(diào)用。
          2. 用戶使用時(shí),同樣是new SyncHook(),里面會(huì)執(zhí)行Hook的構(gòu)造函數(shù)
          3. Hook構(gòu)造函數(shù)會(huì)給this.call賦值為CALL_DELEGATE,但是不會(huì)立即執(zhí)行。
          4. new SyncHook()繼續(xù)執(zhí)行,新建的實(shí)例上的方法hook.complie被覆寫為正確方法。
          5. 當(dāng)用戶調(diào)用hook.call的時(shí)候才會(huì)真正執(zhí)行this._createCall(),這里面會(huì)去調(diào)用this.complie()
          6. 這時(shí)候調(diào)用的complie已經(jīng)是被正確覆寫過的了,所以得到正確的結(jié)果。

          子類SyncBailHook的實(shí)現(xiàn)

          子類SyncBailHook的實(shí)現(xiàn)跟上面SyncHook的也是非常像,只是hook.compile實(shí)現(xiàn)不一樣而已:

          const?Hook?=?require('./Hook');

          function?SyncBailHook(args?=?[])?{
          ????//?基本結(jié)構(gòu)跟SyncHook都是一樣的
          ???const?hook?=?new?Hook(args);
          ????hook.constructor?=?SyncBailHook;

          ????
          ????//?只是compile的實(shí)現(xiàn)是Bail版的
          ??hook.compile?=?function(options)?{
          ????????const?{?taps?}?=?options;
          ????????const?call?=?function(...args)?{
          ????????????const?tapsLength?=?taps.length;
          ????????????for(let?i?=?0;?i?????????????????const?fn?=?this.taps[i];
          ????????????????const?res?=?fn(...args);

          ????????????????if(?res?!==?undefined)?break;
          ????????????}
          ????????}

          ????????return?call;
          ????};
          ????
          ?return?hook;
          }

          SyncBailHook.prototype?=?null;

          抽象代碼工廠

          上面我們通過對(duì)SyncHookSyncBailHook的抽象提煉出了一個(gè)基類Hook,減少了重復(fù)代碼。基于這種結(jié)構(gòu)子類需要實(shí)現(xiàn)的就是complie方法,但是如果我們將SyncHookSyncBailHookcomplie方法拿出來(lái)對(duì)比下:

          SyncHook:

          hook.compile?=?function(options)?{
          ??const?{?taps?}?=?options;
          ??const?call?=?function(...args)?{
          ????const?tapsLength?=?taps.length;
          ????for(let?i?=?0;?i???????const?fn?=?this.taps[i];
          ??????fn(...args);
          ????}
          ??}

          ??return?call;
          };

          SyncBailHook

          hook.compile?=?function(options)?{
          ??const?{?taps?}?=?options;
          ??const?call?=?function(...args)?{
          ????const?tapsLength?=?taps.length;
          ????for(let?i?=?0;?i???????const?fn?=?this.taps[i];
          ??????const?res?=?fn(...args);

          ??????if(?res?!==?undefined)?return?res;
          ????}
          ??}

          ??return?call;
          };

          我們發(fā)現(xiàn)這兩個(gè)complie也非常像,有大量重復(fù)代碼,所以tapable為了解決這些重復(fù)代碼,又進(jìn)行了一次抽象,也就是代碼工廠HookCodeFactoryHookCodeFactory的作用就是用來(lái)生成complie返回的call函數(shù)體,而HookCodeFactory在實(shí)現(xiàn)時(shí)也采用了Hook類似的思路,也是先實(shí)現(xiàn)了一個(gè)基類HookCodeFactory,然后不同的Hook再繼承這個(gè)類來(lái)實(shí)現(xiàn)自己的代碼工廠,比如SyncHookCodeFactory

          創(chuàng)建函數(shù)的方法

          在繼續(xù)深入代碼工廠前,我們先來(lái)回顧下JS里面創(chuàng)建函數(shù)的方法。一般我們會(huì)有這幾種方法:

          1. 函數(shù)申明

            function?add(a,?b)?{
            ??return?a?+?b;
            }
          2. 函數(shù)表達(dá)式

            const?add?=?function(a,?b)?{
            ??return?a?+?b;
            }

          但是除了這兩種方法外,還有種不常用的方法:使用Function構(gòu)造函數(shù)。比如上面這個(gè)函數(shù)使用構(gòu)造函數(shù)創(chuàng)建就是這樣的:

          const?add?=?new?Function('a',?'b',?'return?a?+?b;');

          上面的調(diào)用形式里,最后一個(gè)參數(shù)是函數(shù)的函數(shù)體,前面的參數(shù)都是函數(shù)的形參,最終生成的函數(shù)跟用函數(shù)表達(dá)式的效果是一樣的,可以這樣調(diào)用:

          add(1,?2);????//?結(jié)果是3

          注意:上面的ab形參放在一起用逗號(hào)隔開也是可以的:

          const?add?=?new?Function('a,?b',?'return?a?+?b;');????//?這樣跟上面的效果是一樣的

          當(dāng)然函數(shù)并不是一定要有參數(shù),沒有參數(shù)的函數(shù)也可以這樣創(chuàng)建:

          const?sayHi?=?new?Function('alert("Hello")');

          sayHi();?//?Hello

          這樣創(chuàng)建函數(shù)和前面的函數(shù)申明和函數(shù)表達(dá)式有什么區(qū)別呢?使用Function構(gòu)造函數(shù)來(lái)創(chuàng)建函數(shù)最大的一個(gè)特征就是,函數(shù)體是一個(gè)字符串,也就是說(shuō)我們可以動(dòng)態(tài)生成這個(gè)字符串,從而動(dòng)態(tài)生成函數(shù)體。因?yàn)?code style="font-size:14px;color:rgb(30,107,184);background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;">SyncHook和SyncBailHookcall函數(shù)很像,我們可以像拼一個(gè)字符串那樣拼出他們的函數(shù)體,為了更簡(jiǎn)單的拼湊,tapable最終生成的call函數(shù)里面并沒有循環(huán),而是在拼函數(shù)體的時(shí)候就將循環(huán)展開了,比如SyncHook拼出來(lái)的call函數(shù)的函數(shù)體就是這樣的:

          "use?strict";
          var?_x?=?this._x;
          var?_fn0?=?_x[0];
          _fn0(newSpeed);
          var?_fn1?=?_x[1];
          _fn1(newSpeed);

          上面代碼的_x其實(shí)就是保存回調(diào)的數(shù)組taps,這里重命名為_x,我想是為了節(jié)省代碼大小吧。這段代碼可以看到,_x,也就是taps里面的內(nèi)容已經(jīng)被展開了,是一個(gè)一個(gè)取出來(lái)執(zhí)行的。

          SyncBailHook最終生成的call函數(shù)體是這樣的:

          "use?strict";
          var?_x?=?this._x;
          var?_fn0?=?_x[0];
          var?_result0?=?_fn0(newSpeed);
          if?(_result0?!==?undefined)?{
          ????return?_result0;
          ????;
          }?else?{
          ????var?_fn1?=?_x[1];
          ????var?_result1?=?_fn1(newSpeed);
          ????if?(_result1?!==?undefined)?{
          ????????return?_result1;
          ????????;
          ????}?else?{
          ????}
          }

          這段生成的代碼主體邏輯其實(shí)跟SyncHook是一樣的,都是將_x展開執(zhí)行了,他們的區(qū)別是SyncBailHook會(huì)對(duì)每次執(zhí)行的結(jié)果進(jìn)行檢測(cè),如果結(jié)果不是undefined就直接return了,后面的回調(diào)函數(shù)就沒有機(jī)會(huì)執(zhí)行了。

          創(chuàng)建代碼工廠基類

          基于這個(gè)目的,我們的代碼工廠基類應(yīng)該可以生成最基本的call函數(shù)體。我們來(lái)寫個(gè)最基本的HookCodeFactory吧,目前他只能生成SyncHookcall函數(shù)體:

          class?HookCodeFactory?{
          ????constructor()?{
          ????????//?構(gòu)造函數(shù)定義兩個(gè)變量
          ????????this.options?=?undefined;
          ????????this._args?=?undefined;
          ????}

          ????//?init函數(shù)初始化變量
          ????init(options)?{
          ????????this.options?=?options;
          ????????this._args?=?options.args.slice();
          ????}

          ????//?deinit重置變量
          ????deinit()?{
          ????????this.options?=?undefined;
          ????????this._args?=?undefined;
          ????}

          ????//?args用來(lái)將傳入的數(shù)組args轉(zhuǎn)換為New?Function接收的逗號(hào)分隔的形式
          ????//?['arg1',?'args']?--->??'arg1,?arg2'
          ????args()?{
          ????????return?this._args.join(",?");
          ????}

          ????//?setup其實(shí)就是給生成代碼的_x賦值
          ????setup(instance,?options)?{
          ????????instance._x?=?options.taps.map(t?=>?t);
          ????}

          ????//?create創(chuàng)建最終的call函數(shù)
          ????create(options)?{
          ????????this.init(options);
          ????????let?fn;

          ????????//?直接將taps展開為平鋪的函數(shù)調(diào)用
          ????????const?{?taps?}?=?options;
          ????????let?code?=?'';
          ????????for?(let?i?=?0;?i?????????????code?+=?`
          ????????????????var?_fn${i}?=?_x[${i}];
          ????????????????_fn${i}(${this.args()});
          ????????????`

          ????????}

          ????????//?將展開的循環(huán)和頭部連接起來(lái)
          ????????const?allCodes?=?`
          ????????????"use?strict";
          ????????????var?_x?=?this._x;
          ????????`
          ?+?code;

          ????????//?用傳進(jìn)來(lái)的參數(shù)和生成的函數(shù)體創(chuàng)建一個(gè)函數(shù)出來(lái)
          ????????fn?=?new?Function(this.args(),?allCodes);

          ????????this.deinit();??//?重置變量

          ????????return?fn;????//?返回生成的函數(shù)
          ????}
          }

          上面代碼最核心的其實(shí)就是create函數(shù),這個(gè)函數(shù)會(huì)動(dòng)態(tài)創(chuàng)建一個(gè)call函數(shù)并返回,所以SyncHook可以直接使用這個(gè)factory創(chuàng)建代碼了:

          //?SyncHook.js

          const?Hook?=?require('./Hook');
          const?HookCodeFactory?=?require("./HookCodeFactory");

          const?factory?=?new?HookCodeFactory();

          //?COMPILE函數(shù)會(huì)去調(diào)用factory來(lái)生成call函數(shù)
          const?COMPILE?=?function(options)?{
          ?factory.setup(this,?options);
          ?return?factory.create(options);
          };

          function?SyncHook(args?=?[])?{
          ??const?hook?=?new?Hook(args);
          ????hook.constructor?=?SyncHook;

          ????//?使用HookCodeFactory來(lái)創(chuàng)建最終的call函數(shù)
          ????hook.compile?=?COMPILE;

          ?return?hook;
          }

          SyncHook.prototype?=?null;

          讓代碼工廠支持SyncBailHook

          現(xiàn)在我們的HookCodeFactory只能生成最簡(jiǎn)單的SyncHook代碼,我們需要對(duì)他進(jìn)行一些改進(jìn),讓他能夠也生成SyncBailHookcall函數(shù)體。你可以拉回前面再仔細(xì)觀察下這兩個(gè)最終生成代碼的區(qū)別:

          1. SyncBailHook需要對(duì)每次執(zhí)行的result進(jìn)行處理,如果不為undefined就返回
          2. SyncBailHook生成的代碼其實(shí)是if...else嵌套的,我們生成的時(shí)候可以考慮使用一個(gè)遞歸函數(shù)

          為了讓SyncHookSyncBailHook的子類代碼工廠能夠傳入差異化的result處理,我們先將HookCodeFactory基類的create拆成兩部分,將代碼拼裝的邏輯單獨(dú)拆成一個(gè)函數(shù):

          class?HookCodeFactory?{
          ????//?...
          ???//?省略其他一樣的代碼
          ???//?...

          ????//?create創(chuàng)建最終的call函數(shù)
          ????create(options)?{
          ????????this.init(options);
          ????????let?fn;

          ????????//?拼裝代碼頭部
          ????????const?header?=?`
          ????????????"use?strict";
          ????????????var?_x?=?this._x;
          ????????`
          ;

          ????????//?用傳進(jìn)來(lái)的參數(shù)和函數(shù)體創(chuàng)建一個(gè)函數(shù)出來(lái)
          ????????fn?=?new?Function(this.args(),
          ????????????header?+
          ????????????this.content());?????????//?注意這里的content函數(shù)并沒有在基類HookCodeFactory實(shí)現(xiàn),而是子類實(shí)現(xiàn)的

          ????????this.deinit();

          ????????return?fn;
          ????}

          ????//?拼裝函數(shù)體
          ???//?callTapsSeries也沒在基類調(diào)用,而是子類調(diào)用的
          ????callTapsSeries()?{
          ????????const?{?taps?}?=?this.options;
          ????????let?code?=?'';
          ????????for?(let?i?=?0;?i?????????????code?+=?`
          ????????????????var?_fn${i}?=?_x[${i}];
          ????????????????_fn${i}(${this.args()});
          ????????????`

          ????????}

          ????????return?code;
          ????}
          }

          上面代碼里面要特別注意create函數(shù)里面生成函數(shù)體的時(shí)候調(diào)用的是this.content,但是this.content并沒與在基類實(shí)現(xiàn),這要求子類在使用HookCodeFactory的時(shí)候都需要繼承他并實(shí)現(xiàn)自己的content函數(shù),所以這里的content函數(shù)也是一個(gè)抽象接口。那SyncHook的代碼就應(yīng)該改成這樣:

          //?SyncHook.js

          //?...?省略其他一樣的代碼?...

          //?SyncHookCodeFactory繼承HookCodeFactory并實(shí)現(xiàn)content函數(shù)
          class?SyncHookCodeFactory?extends?HookCodeFactory?{
          ????content()?{
          ????????return?this.callTapsSeries();????//?這里的callTapsSeries是基類的
          ????}
          }

          //?使用SyncHookCodeFactory來(lái)創(chuàng)建factory
          const?factory?=?new?SyncHookCodeFactory();

          const?COMPILE?=?function?(options)?{
          ????factory.setup(this,?options);
          ????return?factory.create(options);
          };

          **注意這里:**子類實(shí)現(xiàn)的content其實(shí)又調(diào)用了基類的callTapsSeries來(lái)生成最終的函數(shù)體。所以這里這幾個(gè)函數(shù)的調(diào)用關(guān)系其實(shí)是這樣的:

          d1fe1c5b629a2f09159287decbb04743.webpimage-20210401111739814

          那這樣設(shè)計(jì)的目的是什么呢為了讓子類content能夠傳遞參數(shù)給基類callTapsSeries,從而生成不一樣的函數(shù)體。我們馬上就能在SyncBailHook的代碼工廠上看到了。

          為了能夠生成SyncBailHook的函數(shù)體,我們需要讓callTapsSeries支持一個(gè)onResult參數(shù),就是這樣:

          class?HookCodeFactory?{
          ????//?...?省略其他相同的代碼?...

          ????//?拼裝函數(shù)體,需要支持options.onResult參數(shù)
          ????callTapsSeries(options)?{
          ????????const?{?taps?}?=?this.options;
          ????????let?code?=?'';
          ????????let?i?=?0;

          ????????const?onResult?=?options?&&?options.onResult;
          ????????
          ????????//?寫一個(gè)next函數(shù)來(lái)開啟有onResult回調(diào)的函數(shù)體生成
          ????????//?next和onResult相互遞歸調(diào)用來(lái)生成最終的函數(shù)體
          ????????const?next?=?()?=>?{
          ????????????if(i?>=?taps.length)?return?'';

          ????????????const?result?=?`_result${i}`;
          ????????????const?code?=?`
          ????????????????var?_fn${i}?=?_x[${i}];
          ????????????????var?${result}?=?_fn${i}(${this.args()});
          ????????????????${onResult(i++,?result,?next)}
          ????????????`
          ;

          ????????????return?code;
          ????????}

          ????????//?支持onResult參數(shù)
          ????????if(onResult)?{
          ????????????code?=?next();
          ????????}?else?{
          ???????????//?沒有onResult參數(shù)的時(shí)候,即SyncHook跟之前保持一樣
          ????????????for(;?i????????????????code?+=?`
          ????????????????????var?_fn${i}?=?_x[${i}];
          ????????????????????_fn${i}(${this.args()});
          ????????????????`

          ????????????}
          ????????}

          ????????return?code;
          ????}
          }

          然后我們的SyncBailHook的代碼工廠在繼承工廠基類的時(shí)候需要傳一個(gè)onResult參數(shù),就是這樣:

          const?Hook?=?require('./Hook');
          const?HookCodeFactory?=?require("./HookCodeFactory");

          //?SyncBailHookCodeFactory繼承HookCodeFactory并實(shí)現(xiàn)content函數(shù)
          //?content里面?zhèn)魅攵ㄖ频膐nResult函數(shù),onResult回去調(diào)用next遞歸生成嵌套的if...else...
          class?SyncBailHookCodeFactory?extends?HookCodeFactory?{
          ????content()?{
          ????????return?this.callTapsSeries({
          ????????????onResult:?(i,?result,?next)?=>
          ????????????????`if(${result}?!==?undefined)?{\nreturn?${result};\n}?else?{\n${next()}}\n`,
          ????????});
          ????}
          }

          //?使用SyncHookCodeFactory來(lái)創(chuàng)建factory
          const?factory?=?new?SyncBailHookCodeFactory();

          const?COMPILE?=?function?(options)?{
          ????factory.setup(this,?options);
          ????return?factory.create(options);
          };


          function?SyncBailHook(args?=?[])?{
          ????//?基本結(jié)構(gòu)跟SyncHook都是一樣的
          ????const?hook?=?new?Hook(args);
          ????hook.constructor?=?SyncBailHook;

          ????//?使用HookCodeFactory來(lái)創(chuàng)建最終的call函數(shù)
          ????hook.compile?=?COMPILE;

          ????return?hook;
          }

          現(xiàn)在運(yùn)行下代碼,效果跟之前一樣的,大功告成~

          其他Hook的實(shí)現(xiàn)

          到這里,tapable的源碼架構(gòu)和基本實(shí)現(xiàn)我們已經(jīng)弄清楚了,但是本文只用了SyncHookSyncBailHook做例子,其他的,比如AsyncParallelHook并沒有展開講。因?yàn)?code style="font-size:14px;color:rgb(30,107,184);background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;">AsyncParallelHook之類的其他Hook的實(shí)現(xiàn)思路跟本文是一樣的,比如我們可以先實(shí)現(xiàn)一個(gè)獨(dú)立的AsyncParallelHook類:

          class?AsyncParallelHook?{
          ????constructor(args?=?[])?{
          ????????this._args?=?args;
          ????????this.taps?=?[];
          ????}
          ????tapAsync(name,?task)?{
          ????????this.taps.push(task);
          ????}
          ????callAsync(...args)?{
          ????????//?先取出最后傳入的回調(diào)函數(shù)
          ????????let?finalCallback?=?args.pop();

          ????????//?定義一個(gè)?i?變量和?done?函數(shù),每次執(zhí)行檢測(cè)?i?值和隊(duì)列長(zhǎng)度,決定是否執(zhí)行?callAsync?的最終回調(diào)函數(shù)
          ????????let?i?=?0;
          ????????let?done?=?()?=>?{
          ????????????if?(++i?===?this.taps.length)?{
          ????????????????finalCallback();
          ????????????}
          ????????};

          ????????//?依次執(zhí)行事件處理函數(shù)
          ????????this.taps.forEach(task?=>?task(...args,?done));
          ????}
          }

          然后對(duì)他的callAsync函數(shù)進(jìn)行抽象,將其抽象到代碼工廠類里面,使用字符串拼接的方式動(dòng)態(tài)構(gòu)造出來(lái)就行了,整體思路跟前面是一樣的。具體實(shí)現(xiàn)過程可以參考tapable源碼:

          Hook類源碼

          SyncHook類源碼

          SyncBailHook類源碼

          HookCodeFactory類源碼

          總結(jié)

          本文可運(yùn)行示例代碼已經(jīng)上傳GitHub,大家拿下來(lái)一邊玩一邊看文章效果更佳:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Engineering/tapable-source-code

          下面再對(duì)本文的思路進(jìn)行一個(gè)總結(jié):

          1. tapable的各種Hook其實(shí)都是基于發(fā)布訂閱模式。
          2. 各個(gè)Hook自己獨(dú)立實(shí)現(xiàn)其實(shí)也沒有問題,但是因?yàn)槎际前l(fā)布訂閱模式,會(huì)有大量重復(fù)代碼,所以tapable進(jìn)行了幾次抽象。
          3. 第一次抽象是提取一個(gè)Hook基類,這個(gè)基類實(shí)現(xiàn)了初始化和事件注冊(cè)等公共部分,至于每個(gè)Hookcall都不一樣,需要自己實(shí)現(xiàn)。
          4. 第二次抽象是每個(gè)Hook在實(shí)現(xiàn)自己的call的時(shí)候,發(fā)現(xiàn)代碼也有很多相似之處,所以提取了一個(gè)代碼工廠,用來(lái)動(dòng)態(tài)生成call的函數(shù)體。
          5. 總體來(lái)說(shuō),tapable的代碼并不難,但是因?yàn)橛袃纱纬橄螅麄€(gè)代碼架構(gòu)顯得不那么好讀,經(jīng)過本文的梳理后,應(yīng)該會(huì)好很多了。


          覺得博主寫得還可以的話,不要忘了分享、點(diǎn)贊、在看三連哦~

          長(zhǎng)按下方圖片,關(guān)注進(jìn)擊的大前端,獲取更多的優(yōu)質(zhì)原創(chuàng)文章~?

          參考資料

          tapable用法介紹:https://juejin.cn/post/6939794845053485093

          tapable源碼地址:https://github.com/webpack/tapable


          瀏覽 50
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  超碰在线中文字幕 | 青青青青艹 | 人人色人人草 | 伊人大香蕉视频 | 欧美人与性口牲恔配上海 |