CommonJS與ES6 Module本質區別,commonJS對模塊依賴解決是「動態的」,ES6 Module是靜態的node
module | 模塊依賴 | 含義 | 備註 |
---|---|---|---|
CommonJS | 動態 | 模塊依賴關係的創建發生在代碼運行階段 | node命令執行es6混用 .cjs |
ES6 Module | 靜態 | 模塊依賴關係創建發生在代碼編譯階段 | node命令執行es模塊 --experimental-modules |
// example-1/test.cjs
module.exports = { name: 'test' };
// example-1/index.cjs
const { name } = require('./test.js');
複製代碼
CommonJS規範,當模塊A加載模塊B時,例如上面index.js加載test.js,會執行test.js中的代碼, module.exports對象會做爲require函數的返回值被加載。require的模塊路徑能夠動態指定,支持 傳入一個表達式,也能夠經過if語句判斷是否加載某個模塊。所以在CommonJS模塊被執行前,並不 能明確依賴關係,模塊的導入導出發生在代碼運行時。webpack
// example-1/test.js
export const name = 'test';
// node example-1/index.js
import { name } from './test.js';
複製代碼
ES6 Module的導入導出都是聲明式的,它不支持導入路徑是一個表達式,全部導入導出 必須位於模塊的頂層做用域(不能放在if語句中)。所以ES6 Module是一個靜態的模塊結構,在ES6 代碼編譯階段就能夠分析出模塊的依賴關係。git
導入模塊時,CommonJS是導出值的拷貝,ES6 Module是值的動態映射,而且這個映射是自讀的。es6
index.cjs中count是對test中的count的值拷貝,所以在調用add時,改變了test中的count,可是不會對index中的 count形成影響github
// example-2/test.cjs
var count = 0;
module.exports = {
count,
add () {
count++;
return count;
}
}
// example-2/index.cjs
let { count, add } = require('./test.cjs');
console.log(count); // 0 這裏的count是對test.js中的count的拷貝
add();
console.log(count); // 0 test.js中值改變不會形成index中的拷貝值影響
count += 1;
console.log(count); // 1 index中拷貝值改變
複製代碼
ES6 Module中導入的變量時對原有值的動態映射,index中調用add,count也會變化,咱們不能對ES6 Module 導入的變量進行更改web
// example-2/test.js
let count = 0;
export {
count,
add () {
count++;
}
}
// example-2/index.js
import { count, add } from './test.js';
console.log(count); // 0
add();
console.log(count); // 1
count++; // TypeError: Assignment to constant variable.
複製代碼
循環依賴指模塊A依賴於模塊B,同時模塊B依賴於模塊A(工程中應該儘可能避免循環依賴,複雜度會提高,依賴關係不清晰), 若是A依賴B,B依賴C,C依賴D......最後一圈回來D又依賴A,那麼多中間模塊之間的依賴關係就很難梳理了bash
// example-3/a.cjs
const b = require('./b.cjs');
console.log('b:', b);
module.exports = 'a.cjs';
// example-3/b.cjs
const a = require('./a.cjs');
console.log('a:', a);
module.exports = 'b.cjs';
// example-3/index.cjs
require('./a.cjs');
複製代碼
上面的例子,a依賴b,b依賴a函數
// 預期輸出
a: a.cjs
b: b.cjs
// 實際輸出
a: {}
b: b.cjs
複製代碼
實際輸出時a是一個空對象工具
webpack實現__webpack_require__,初始化一個module對象放入installedModules中,當這個模塊再次 被引用到時直接從installedModules裏面取值,此時他就是一個空對象,解釋了上面例子的現象優化
function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
複製代碼
ES6 Module方式實現循環依賴,下面的例子運行時報錯了,沒法在初始化以前訪問"a"
// example-3/a.js
import b from './b.js';
console.log('b:', b);
export default 'a.js';
// example-3/b.js
import a from './a.js';
console.log('a:', a);
export default 'b.js';
// example-3/index.js
import a from './a.js'; // ReferenceError: Cannot access 'a' before initialization
複製代碼
改一改上面的例子,使其能正常運行
// example-4/a.js
import b from './b.js';
function a (invoker) {
console.log(invoker + 'invokes a.js');
b('a.js');
}
export default a;
// example-4/b.js
import a from './a.js';
function b (invoker) {
console.log(invoker + 'invokes b.js');
a('b.js');
}
export default b;
// example-4/index.js
import a from './a.js';
a('index.js');
// 執行結果
index.jsinvokes a.js
a.jsinvokes b.js
b.jsinvokes a.js
a.jsinvokes b.js
複製代碼
上面的例子正確的打印了a與b的循環依賴,來分析下代碼執行過程