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區別
最明顯的區別就是在模組定義時對依賴的處理不同
- AMD推崇依賴前置,在定義模組的時候就要聲明其依賴的模組
- 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的變數或方法。