模仿RequireJs的用法實現一個低配版的模塊加載器

Contents

  1. 前言
  2. 回顧RequireJs的基本用法
  3. 實現原理
  4. 使用方法
  5. 總結

前言

前段時間一直想用單頁開發技術寫一個本身的我的網站(使用es2015),寫了一部分以後,發現單頁應用由於只有一個頁面,因此第一次加載index.html時就要下載全部js文件,而且爲了好管理各個部分的狀態,須要劃分頁面的各個功能區爲各個模塊,es2015自己是不支持一些模塊規範的(好比AMD、CMD、CommonJs等),因此只能這樣模擬實現:javascript

// global
  var spa = (function(){...})();

  // module blog
  spa.blog = (function(){
    ...
    return {
      do1: do1,
      do2: do2,
    };
  })();

  // module model
  spa.model = (function(){...})();

  // module shell
  spa.model = (function(){...})();

而且各個模塊之間又存在一些依賴關係,在index.html裏面寫script標籤來載入模塊時須要寫不少個,同時也要根據依賴關係來肯定書寫順序,頁面邏輯混亂,以下:html

<script type="text/javascript" src="/javascripts/spa.utils.js"></script>
  <script type="text/javascript" src="/javascripts/spa.model.js"></script>
  <script type="text/javascript" src="/javascripts/spa.mock.js"></script>
  <script type="text/javascript" src="/javascripts/spa.chat.js"></script>
  <script type="text/javascript" src="/javascripts/spa.blog.js"></script>
  <script type="text/javascript" src="/javascripts/spa.action.js"></script>
  <script type="text/javascript" src="/javascripts/spa.shell.js"></script>

以前用過RequireJs(一個流行的JavaScript模塊加載器),它是用同構js的架構來寫的,因此node.js環境下也能使用。我想本身能夠嘗試一下寫一個低配版的js模塊加載器 requireJs-nojsja 來應付一下我這個單頁網站,固然只是大體模仿了主要功能。java

回顧RequireJs的基本用法

1. 配置模塊信息

requirejs.config({
          baseUrl: '/javascripts',  // 配置根目錄
          paths: {
            moduleA: 'a.js',
            moduleB: 'b.js',
            moduleC: 'c.js',
          },
          shim: {  // 配置不遵循amd規範的模塊
            moduleC: {
              exports: 'log',
              deps: ['moduleA']
            }
          },
      });

2. 定義一個模塊

define(name, ['moduleA', 'moduleB'], function(a, b){
    ...
    return {
      do: function() {
        a.doSomething();
        b.doAnother();
      }
    };
  });

3. 引用一個模塊

// 引用模塊
  require(['moduleA', 'moduleB'], function(a, b) {
    a.doSomething();
    b.doAnother();
  });

實現原理

1. config方法肯定各個模塊的依賴關係

/* 記錄模塊訪問地址和模塊的依賴等信息 */
  Require.config({
    baseUrl: '/javascripts/',
    paths: {
      'moduleA': './moduleA.js',  // 相對於當前目錄
      'moduleB': '/javascripts/moduleB.js',  // 不使用baseUrl
      'moduleC': 'moduleC.js',

      'moduleD': {
        url: 'moduleD.js',
        deps: ['moduleE', 'moduleF'],
      },
      ...
    },
    shim: {
      'moduleH': {
        url: 'moduleH.js',
        exports: 'log',
      },
    }
  });

2. 數據請求請求過程分析

(1) config配置模塊信息時並不會觸發網絡請求
(2) 在index.js主入口文件裏使用require方法引用多個模塊時,根據config配置文件構造一下全部模塊的依賴分析樹。按深度優先或是廣度優先來遍歷這個依賴樹,將全部依賴按照依賴順序放進一個數組,最後進行數組去重處理,由於會出現依賴重複的狀況node

var dependsTree = new Tree('dependsTree');
  var dependsArray = [];
  var dependsFlag = {};  // 解決循環依賴

  // 建立樹
  setDepends(depends, dependsTree);
  // 獲得依賴數組
  sortDepends(dependsArray, dependsTree);
  // 數據去重
  arrayFilter(dependsArray);

  return dependsArray;

(3) 建立XHR對象異步下載數組裏面的全部js文件,按照依賴順序挨個解析js代碼,解析完成後觸發回調函數,回調函數裏傳入各個模塊的引用git

// ajax下載代碼文件
  Utils.request(url, 'get', null, function(responseText){
    // 暫時保存
    _temp[module_name] = responseText;
  });
    
  // 文件下載完成後eval解析代碼
  array.map(function(jsText){
    ...
    eval(jsText);
    ...
  });
    
  // 調用回調函數
  callback.apply(null, [dep1, dep2, dep3]);

使用方法

詳細說明: github README.mdgithub

總結

  1. 下載js代碼時我用了ajax來實現,因此對於跨域文件和CDN會有點問題,這個能夠改爲建立script標籤,指定標籤src,最後將document.head.appendChild(script),這樣來解決,其它的諸如使用XMLHttpRequest 2.0,iframe等也能夠的,能夠實驗一下。
  2. 解析代碼時我用了eval的方法,這個eval在JavaScript裏面是衆說紛紜,能夠看看這個,若是是用了上面建立script標籤的方法的話,就不用本身eval了。
  3. 發現一個bug,存在循環依賴時,代碼會報錯,還沒去解決。RequireJs是這樣處理的:模塊a依賴b,同時b依賴a,這種狀況下b的模塊函數被調用時,被傳入的a是undefined,因此須要本身在b裏面手動require一下a。
相關文章
相關標籤/搜索