JS模組化規範總結(面試必備良藥)

  • 2019 年 12 月 20 日
  • 筆記

文章說明

本文為我之前總結的筆記,因為內容在面試中問得比較多,因而搬運過來,作為面試系列的文章之一。

簡介

規範JavaScript的模組定義和載入機制,降低了學習和使用各種框架的門檻,能夠以一種統一的方式去定義和使用模組,提高開發效率,降低了應用維護成本。 模組化解決的問題:

  • 命名衝突
  • 文件依賴

commonJS

1、模組可以多次載入,但是只會在第一次載入時運行一次,然後運行結果就被快取了,以後再載入,就直接讀取快取結果。要想讓模組再次運行,必須清除快取。 2、模組載入會阻塞接下來程式碼的執行,需要等到模組載入完成才能繼續執行——同步載入。

適用場景:伺服器環境,nodejs的模組規範是參照commonJS實現的。

用法:

1、導入:require('路徑') 2、導出:module.exports和exports

// a.js  // 相當於這裡還有一行:var exports = module.exports;程式碼  exports.a = 'Hello world';  // 相當於:module.exports.a = 'Hello world';    // b.js  var moduleA = require('./a.js');  console.log(moduleA.a);     // 列印出hello world

注意:module.exports和exports的區別是exports只是對module.exports的一個引用,相當於Node為每個模組提供一個exports變數,指向module.exports。這等同在每個模組頭部,有一行var exports = module.exports;這樣的命令。

AMD

1、非同步載入 2、管理模組之間的依賴性,便於程式碼的編寫和維護。

適用場景:瀏覽器環境,requireJS是參照AMD規範實現的

用法: 1、導入:require(['模組名稱'], function ('模組變數引用'){// 程式碼}); 2、導出:define(function (){return '值');

// a.js  define(function (){    return {     a:'hello world'    }  });  // b.js  require(['./a.js'], function (moduleA){      console.log(moduleA.a); // 列印出:hello world  });

CMD

1、CMD是在AMD基礎上改進的一種規範,和AMD不同在於對依賴模組的執行時機處理不同,CMD是就近依賴,而AMD是前置依賴。

適用場景:瀏覽器環境,seajs是參照CMD規範實現的,requireJS的最新的幾個版本也是部分參照了CMD規範的實現。

用法:

1、導入:define(function(require, exports, module) {}); 2、導出:define(function (){return '值');

// a.js  define(function (require, exports, module){    exports.a = 'hello world';  });  // b.js  define(function (require, exports, module){      var moduleA = require('./a.js');      console.log(moduleA.a); // 列印出:hello world  });

AMD與CMD區別

最明顯的區別就是在模組定義時對依賴的處理不同

  1. AMD推崇依賴前置,在定義模組的時候就要聲明其依賴的模組
  2. CMD推崇就近依賴,只有在用到某個模組的時候再去require

AMD和CMD最大的區別是對依賴模組的執行時機處理不同

很多人說requireJS是非同步載入模組,SeaJS是同步載入模組,這麼理解實際上是不準確的,其實載入模組都是非同步的,只不過AMD依賴前置,js可以方便知道依賴模組是誰,立即載入,而CMD就近依賴,需要使用時把模組變為字元串解析一遍才知道依賴了那些模組,這也是很多人詬病CMD的一點,犧牲性能來帶來開發的便利性,實際上解析模組用的時間短到可以忽略

為什麼我們說兩個的區別是依賴模組執行時機不同,為什麼很多人認為ADM是非同步的,CMD是同步的(除了名字的原因。。。)

同樣都是非同步載入模組,AMD在載入模組完成後就會執行改模組,所有模組都載入執行完後會進入require的回調函數,執行主邏輯,這樣的效果就是依賴模組的執行順序和書寫順序不一定一致,看網路速度,哪個先下載下來,哪個先執行,但是主邏輯一定在所有依賴載入完成後才執行

CMD載入完某個依賴模組後並不執行,只是下載而已,在所有依賴模組載入完成後進入主邏輯,遇到require語句的時候才執行對應的模組,這樣模組的執行順序和書寫順序是完全一致的

這也是很多人說AMD用戶體驗好,因為沒有延遲,依賴模組提前執行了,CMD性能好,因為只有用戶需要的時候才執行的原因

UMD

1、兼容AMD和commonJS規範的同時,還兼容全局引用的方式。

適用場景:瀏覽器或伺服器環境

用法:

無導入導出規範,只有如下的一個常規寫法:

(function (root, factory) {      if (typeof define === 'function' && define.amd) {          //AMD          define(['jquery'], factory);      } else if (typeof exports === 'object') {          //Node, CommonJS之類的          module.exports = factory(require('jquery'));      } else {          //瀏覽器全局變數(root 即 window)          root.returnExports = factory(root.jQuery);      }  }(this, function ($) {      //方法      function myFunc(){};      //暴露公共方法      return myFunc;  }));

ESM(ES6 Module)

1、按需載入(編譯時載入) 2、import和export命令只能在模組的頂層,不能在程式碼塊之中(如:if語句中),import()語句可以在程式碼塊中實現非同步動態按需動態載入

適用場景:瀏覽器或伺服器環境(以後可能支援)

用法:

1、導入:import {模組名A,模組名B…} from '模組路徑' 2、導出:export和export default 3、import('模組路徑').then()方法

/*錯誤的寫法*/  // 寫法一  export 1;    // 寫法二  var m = 1;  export m;    // 寫法三  if (x === 2) {    import MyModual from './myModual';  }    /*正確的三種寫法*/  // 寫法一  export var m = 1;    // 寫法二  var m = 1;  export {m};    // 寫法三  var n = 1;  export {n as m};    // 寫法四  var n = 1;  export default n;    // 寫法五  if (true) {      import('./myModule.js')      .then(({export1, export2}) => {        // ...·      });  }    // 寫法六  Promise.all([    import('./module1.js'),    import('./module2.js'),    import('./module3.js'),  ])  .then(([module1, module2, module3]) => {     ···  });

注意:export只支援對象形式導出,不支援值的導出,export default命令用於指定模組的默認輸出,只支援值導出,但是只能指定一個,本質上它就是輸出一個叫做default的變數或方法。