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

          如何實現(xiàn)一個異步模塊加載器

          共 8366字,需瀏覽 17分鐘

           ·

          2021-01-04 01:45


          作者:youngwind

          原文鏈接:https://github.com/youngwind/blog/issues/98

          引言

          異步是前端中的一個重點。

          今天就結合模塊,和大家講分享一下:如何實現(xiàn)一個簡單的模塊加載器。


          正文

          最近參考 require.js 的API,自己動手實現(xiàn)了一個簡單的異步模塊加載器: fake-requirejs。

          為什么要做這樣一個東西呢?

          原因是:我一直覺得自己對模塊化這方面的理解不夠深入。

          即便用了很長時間的 webpack,看了很多模塊化相關的資料,比如:

          • 模塊化的發(fā)展歷史
          • amd
          • commonjs 和 cmd規(guī)范之爭

          等等。

          然而,我依然覺得自己的理解流于表面,所以決定自己動手實現(xiàn)一個。

          目標的選擇

          本來一開始的目標是webpack的,但是后來考慮到:

          webpack是建立在模塊化基礎上的一個構建工具。

          且webpack的實現(xiàn)也相當?shù)膹碗s,而我希望能夠刻意區(qū)分開模塊化構建這兩個概念。

          因為這有助于我集中有限的精力研究模塊化這一個概念,所以后來決定實現(xiàn)requirejs,這是一個相對來說比較簡單的異步模塊加載器。

          雖然現(xiàn)在使用它的人已經越來越少了,但是正因為其簡單和純粹,倒是非常適合現(xiàn)在的我。

          注:請確保掌握了requirejs的基本用法再往下閱讀。

          剛開始敲代碼的時候,我就在想如何實現(xiàn)require函數(shù)和define函數(shù),但是后來我發(fā)現(xiàn)我錯了,因為這陷入了面向過程編程的誤區(qū),正確的方式應該是面向對象編程。

          所以,我重新進行了思考。

          問題:這里都有哪些類型的對象呢?

          答案:至少有模塊(Module)這一類對象

          那模塊類對象有哪些數(shù)據(jù)呢?

          Module.id       // 模塊id
          Module.name     // 模塊名字
          Module.src      // 模塊的真實的uri路徑
          Module.dep      // 模塊的依賴
          Module.cb       // 模塊的成功回調函數(shù)
          Module.errorFn  // 模塊的失敗回調函數(shù)
          Module.STATUS   // 模塊的狀態(tài)(等待中、正在網(wǎng)絡請求、準備執(zhí)行、執(zhí)行成功、出現(xiàn)錯誤……)

          又有哪些對應的操作這些數(shù)據(jù)的方法呢?

          Module.prototype.init           // 初始化,用來賦予各種基本值
          Module.prototype.fetch          // 通過網(wǎng)絡請求獲取模塊
          Module.prototype.analyzeDep     // 分析、處理模塊的依賴
          Module.prototype.execute        // 運算該模塊

          依賴分析與處理

          順著上面的思路一步步寫,我碰到了一個難點:

          如何分析和處理模塊的依賴?

          舉個例子:

          // 入口main.js
          require(['a''b'], function (a, b{
              a.hi();
              b.goodbye();
          }, function ({
              console.error('Something wrong with the dependent modules.');
          });

          我們的目標是:

          當模塊a和b都準備好之后,再執(zhí)行成功回調函數(shù);一旦a或b有任意一個失敗,都執(zhí)行失敗回調函數(shù)。

          這個跟使用Promise.allPromise.race很像,但這一次我們是要實現(xiàn)它們。

          怎么辦呢?

          我想了一個方法:記數(shù)法, 分兩步走。

          Module原型新增Module.depCount屬性,初始值為該模塊依賴模塊數(shù)組的長度。

          假如 depCount === 0,說明該模塊依賴的模塊都已經運算好了,通過setter觸發(fā)執(zhí)行該模塊。

          某模塊執(zhí)行成功之后,Module.STATUS === 5,通過setter觸發(fā)下一步。

          通過對象mapDepToModule,查找到依賴與該模塊的所有模塊,那么讓那些模塊都執(zhí)行depCount--。

          注:對象mapDepToModule的作用是:

          映射被依賴模塊到依賴模塊之間的關系。

          結構如下圖所示:

          舉個例子:

          當模塊a準備好之后,我們就遍歷mapDepToModule['a']對應的數(shù)組,里面的每一項都執(zhí)行depCount--。

          下面是一些關鍵的代碼:

          Module.prototype.analyzeDep = function ({
              // ...
              let depCount = this.dep ? this.dep.length : 0;
              Object.defineProperty(this'depCount', {
                  get() {
                      return depCount;
                  },
                  set(newDepCount) {
                      depCount = newDepCount;
                      if (newDepCount === 0) {
                          console.log(`模塊${this.name}的依賴已經全部準備好`);
                          this.execute();  // 如果depCount===0,執(zhí)行該模塊
                      }
                  }
              });
              this.depCount = depCount;
              // ...
          };

          Object.defineProperty(this'status', {
              get () {
                  return status;
              },
              set (newStatus) {
                  status = newStatus;
                  if (status === 5) {
                       // 假如某個模塊已經準備好了(STATUS===5),
                       // 那么找出依賴于這個模塊的所有模塊,讓他們都執(zhí)行depCount--
                      let depedModules = mapDepToModule[this.name];
                      if (!depedModules) return;
                      depedModules.forEach((module) => {
                          setTimeout(() => {
                              module.depCount--;
                          });
                      });
                  }
              }
          })

          雖然我們都說循環(huán)依賴是一種不好的現(xiàn)象,應該在設計之初盡量避免。

          但是,隨著項目越滾越大,誰又能保證一定不會出現(xiàn)?

          所以:

          作為一個合格的模塊加載器,必須解決循環(huán)依賴的問題。

          那么,讓我們先來看看別人是怎么處理的吧。

          • Commonjs和ES6的循環(huán)依賴 http://www.ruanyifeng.com/blog/2015/11/circular-dependency.html

          • seajs的循環(huán)依賴 https://github.com/seajs/seajs/issues/732

          • requirejs的循環(huán)依賴 http://requirejs.cn/docs/api.html#circular

          http://www.ruanyifeng.com/blog/2015/11/circular-dependency.html

          這里我們不討論各種處理方式孰優(yōu)孰劣,我們只關注:

          如何實現(xiàn)requireJS API文檔中那樣的功能?

          仔細觀察下面的例子:

          a 與 b 出現(xiàn)循環(huán)依賴:

          // main.js
          require(['a','b'], function (a, b{
              a.hi();
              b.goodbye();
          }, function ({
              console.error('Something wrong with the dependent modules.');
          });

          // a.js
          define(['b'],function (b{
              var hi = function ({
                  console.log('hi');
              };

              b.goodbye();
              return {
                  hi: hi
              }
          });

          // b.js
          define(['require''a'], function (require{
              var goodbye = function ({
                  console.log('goodbye');
              };
              // 因為在運算b的時候,a還沒準備好,所以不能直接拿到a,只能用require再發(fā)起一次新的任務
              require(['a'], function (a{
                  a.hi();
              });

              return {
                  goodbye: goodbye
              }
          });

          我們能看到:

          模塊b的回調函數(shù)中,并不能直接引用到a,需要使用require方法包住。

          那么問題來了:

          在原先的設計中, 每一個define是跟一個模塊一一對應的,require只能用一次,用于主入口模塊(如:main.js)的加載。

          但是,現(xiàn)在在模塊b的回調函數(shù)中,又出現(xiàn)require(['a']),這顯然是亂套了。

          至此,我發(fā)現(xiàn)require不應該僅僅是用于主入口模塊的加載,require應該對應更高層次的抽象概念:我將它命名為:任務(Task),這是一個有別于Module的新的類。

          每一次調用require,相當于新建一個Task(任務)。

          這個任務的功能是:當任務的所有依賴都準備好之后,執(zhí)行該任務的成功回調函數(shù)。

          有沒有發(fā)現(xiàn)這個Task原型與Module很像?

          它們都有依賴、回調、狀態(tài),都需要分析依賴、執(zhí)行回調函數(shù)等方法。

          但是又有些不同,比如Task沒有網(wǎng)絡請求,所以不需要fetch這樣的方法。

          所以,我讓Task繼承了Module,然后重寫某些方法。

          關鍵代碼如下:

          // before
          require = function (dep, cb, errorFn{
              // mainEntryModule是主入口模塊
              modules[mainEntryModule.name] = mainEntryModule;
              mainEntryModule.dep = dep;
              mainEntryModule.cb = cb;
              mainEntryModule.errorFn = errorFn;
              mainEntryModule.analyzeDep();
          };

          // after
          require = function (dep, cb, errorFn{
              let task = new Task(dep, cb, errorFn);
              task.analyzeDep();
          };

          // 引入新的類: Task(任務)
          function Task(dep, cb, errorFn{
              this.tid = ++tid;
              this.init(dep, cb, errorFn);
          }

          // Task類繼承于Module類
          Task.prototype = Object.create(Module.prototype);


          至此,我們就完成了一個簡單的異步模塊加載器。


          最后


          • 歡迎加我微信(winty230),拉你進技術群,長期交流學習...

          • 歡迎關注「前端Q」,認真學前端,做個專業(yè)的技術人...

          點個在看支持我吧
          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  成人免费视屏 | 大香蕉久久依人网站 | 成人精品在线 | 国产在线日本在线 | 日本一级视频免费看 |