JavaScript模組化
規範JavaScript的模組定義和載入機制,降低了學習和使用各種框架的門檻,能夠以一種統一的方式去定義和使用模組,提高開發效率,降低了應用維護成本。
- 命名衝突
- 檔案依賴
差異
- AMD 與 CMD:
- AMD是 RequireJS 在推廣過程中對模組定義的規範化產出。
- CMD是 SeaJS 在推廣過程中對模組定義的規範化產出。
- CMD推崇依賴就近,AMD推崇依賴前置。
- ES Module與CommonJS:
- CommonJS模組是物件,是執行時載入,執行時才把模組掛載在exports之上(載入整個模組的所有),載入模組其實就是查詢物件屬性。
- ES Module不是物件,是使用export顯示指定輸出,再通過import輸入。此法為編譯時載入,編譯時遇到import就會生成一個只讀引用。等到執行時就會根據此引用去被載入的模組取值。所以不會載入模組所有方法,僅取所需。
- CommonJS 模組輸出的是一個值的拷貝,ES6 模組輸出的是值的引用。
- CommonJS 模組是執行時載入,ES6 模組是編譯時輸出介面
- CommonJS與AMD/CMD:
- AMD/CMD是CommonJS在瀏覽器端的解決方案。
- CommonJS是同步載入(程式碼在本地,載入時間基本等於硬碟讀取時間)。
- AMD/CMD是非同步載入(瀏覽器必須這麼做,程式碼在服務端)
- UMD與AMD/CMD
- UMD(Universal Module Definition)是AMD和CommonJS的糅合,跨平臺的解決方案。
- AMD模組以瀏覽器第一的原則發展,非同步載入模組。
- CommonJS模組以伺服器第一原則發展,選擇同步載入,它的模組無需包裝(unwrapped modules)。
- UMD先判斷是否支援Node.js的模組(exports)是否存在,存在則使用Node.js模組模式。再判斷是否支援AMD(define是否存在),存在則使用AMD方式載入模組。
用法
- CommonJS
- 匯出使用module.exports,也可以exports。就是在此物件上掛屬性。exports指向module.exports,即exports= module.exports
- 載入模組使用require('xxx')。相對、絕對路徑均可。預設引用js,可以不寫.js字尾
// commonjs
module.exports.add = function add(params) {
return ++params;
}
exports.sub = function sub(params) {
return --params;
}
// index.js
var common = require('./commonjs');
console.log(common.sub(1));
console.log(common.add(1));
複製程式碼
- AMD/RequireJS
- 定義模組:define(id?, dependencies?, factory)
- 依賴有三個預設的,即"require", "exports", "module"。順序個數均可視情況
- 如果忽略則factory預設此三個傳入引數
- id一般是不傳的,預設是檔名
- 載入模組:require([module], factory)
- 定義模組:define(id?, dependencies?, factory)
// a.js
define(["b", "require", "exports"], function(b, require, exports) {
console.log("a.js執行");
console.log(b);
// 暴露api可以使用exports、module.exports、return
exports.a = function() {
return require("b");
}
})
// b.js
define(function() {
console.log('b.js執行');
console.log(require);
console.log(exports);
console.log(module);
return 'b';
})
// index.js
// 支援Modules/Wrappings寫法,注意dependencies得是空的,且factory引數不可空
define(function(require, exports, module) {
console.log('index.js執行');
var a = require('a');
var b = require('b');
})
// index.js
require(['a', 'b'], function(a, b) {
console.log('index.js執行');
})
複製程式碼
- CMD/SeaJS
- 定義模組:define(factory)
- require, exports, module引數順序不可亂
- 暴露api方法可以使用exports、module.exports、return
- 與requirejs不同的是,若是未暴露,則返回{},requirejs返回undefined
- 載入模組:require
- 定義模組無需列依賴,它會呼叫factory的toString方法對其進行正則匹配以此分析依賴
- 預先下載,延遲執行
- 定義模組:define(factory)
// a.js
define(function(require, exports, module) {
console.log('a.js執行');
console.log(require);
console.log(exports);
console.log(module);
})
// b.js
define(function(require, module, exports) {
console.log('b.js執行');
console.log(require);
console.log(exports);
console.log(module);
})
// index.js
define(function(require) {
var a = require('a');
var b = require('b');
console.log(a);
console.log(b);
})
複製程式碼
- ES Module
- 輸出/export
- 輸入/import
- 輸入的模組變數是不可重新賦值的,它只是個可讀引用,不過卻可以改寫屬性
// 報錯1
export 1;
// 報錯2
const m = 1;
export m;
// 介面名與模組內部變數之間,建立了一一對應的關係
// 寫法1
export const m = 1;
// 寫法2
const m = 1;
export { m };
// 寫法3
const m = 1;
export { m as module };
複製程式碼
// 類似於物件解構
// module.js
export const m = 1;
// index.js
// 注意,這裡的m得和被載入的模組輸出的介面名對應
import { m } from './module';
// 若是想為輸入的變數取名
import { m as m1 } './module';
// 值得注意的是,import是編譯階段,所以不能動態載入,比如下面寫法是錯誤的。因為'a' + 'b'在執行階段才能取到值,執行階段在編譯階段之後
import { 'a' + 'b' } from './module';
// 若是隻是想執行被載入的模組,如下
// 值得注意的是,即使載入兩次也只是執行一次
import './module';
// 整體載入
import * as module from './module';
複製程式碼
- 總結:
區別項 | es模組化 | commonJS | AMD |
---|---|---|---|
可用於服務端還是瀏覽器 | 服務端和瀏覽器 | 服務端 | 瀏覽器 |
模組依賴關係何時確定(即:何時載入模組) | 編譯時 | 執行時 | 執行時 |
設計思想 | 儘量的靜態化 | ||
模組是不是物件 | 不是 | 是 | |
是否整體載入模組(即載入的所有方法) | 否 | 是 | |
是否是動態更新(即通過介面,可以取到模組內部實時的值) | 是。es module輸出的是值的引用 | 不是。commonJS模組輸出的是值的拷貝,不存在動態更新 | |
模組變數是否是隻讀的 | v是。原因:ES6 輸入的模組變數,只是一個“符號連線”,所以這個變數是隻讀的,對它進行重新賦值會報錯。 |
-
commonJS模組就是物件,整體載入模組(即載入的所有方法)
-
ES6 模組不是物件,而是通過export命令顯式指定輸出的程式碼,再通過import命令輸入。
-
export命令規定的是對外的介面,必須與模組內部的變數建立一一對應關係
-
export語句輸出的介面,與其對應的值是動態繫結關係,即通過該介面,可以取到模組內部實時
-
export命令和import命令可以出現在模組的任何位置,只要處於模組頂層就可以。 如果處於塊級作用域內,就會報錯,這是因為處於條件程式碼塊之中,就沒法做靜態優化了,違背了ES6模組的設計初衷。
-
import命令具有提升效果,會提升到整個模組的頭部,首先執行。