CommonJS與ES6 Module

CommonJS與ES6 Module本質區別,commonJS對模塊依賴解決是「動態的」,ES6 Module是靜態的node

module 模塊依賴 含義 備註
CommonJS 動態 模塊依賴關係的創建發生在代碼運行階段 node命令執行es6混用 .cjs
ES6 Module 靜態 模塊依賴關係創建發生在代碼編譯階段 node命令執行es模塊 --experimental-modules

CommonJS

// 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

ES6 Module

// 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

ES6的優點

  • 死代碼檢測和排除,經過靜態分析工具檢測出哪些模塊沒被調用過。好比引入工具類庫時,工程可能只 用到了某一個接口,但可能將整個工具包都加載進來了,未被調用的代碼永遠不會被執行。經過靜態分析 能夠在打包時去掉這些未使用的模塊,減小打包資源體積。
  • 模塊變量類型檢查,js屬於動態類型語言,不會再代碼執行前檢查類型錯誤。例如將字符串類型進行函數調用。 ES6 Module的靜態模塊結構能夠確保模塊之間傳遞的值或接口類型正確。
  • 編譯器優化,CommonJS本質上是導入一個對象,ES6 Module支持導入變量,減小了引用層級,程序效率更高。

值拷貝與動態映射

導入模塊時,CommonJS是導出值的拷貝,ES6 Module是值的動態映射,而且這個映射是自讀的。es6

  • CommonJS中的值拷貝

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

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是一個空對象工具

  1. index.js導入了a.js
  2. a.js第一行導入了b.js,這時代碼進入b.js中繼續執行
  3. b.js中第一行導入了a.js,這時產生了循環依賴,這時執行權還在b.js上,b.js直接獲取到的是module.exports, 此時a.js未執行完,所以導出的是一個默認空對象
  4. b.js執行完,導出b.js,執行迴歸到a.js中
  5. a.js繼續執行,打印b: b.js

webpack_require

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的循環依賴,來分析下代碼執行過程

  1. index.js導入了a.js,a得到執行權開始執行
  2. a引入了b.js,b得到執行權
  3. b執行,b引入了a,並聲明瞭b方法,b方法中調用到了a,此時a還沒執行完,所以a是不能訪問的
  4. b執行完,回到a中,a定義了a方法,a方法調用了b,導出a方法,這個時候由於ES6 Module是動態映射的, 因此b中a方法已經有定義了
  5. 執行權回到index中,執行a方法

大佬留步歡迎Star

相關文章
相關標籤/搜索