CommonJS 與 ES Module 的區別

在 JavaScript 開發中,模組化系統扮演著重要角色,它幫助我們管理和組織代碼。CommonJS 和 ES Module 是兩種主要的模組化標準,它們在模組的加載、導出機制和兼容性等方面存在顯著區別。本文將詳細比較這兩種模組系統的主要特點和區別。

1. 模組加載方式

CommonJS:

  • 同步加載:CommonJS 採用同步加載模組的方式。這意味著當使用 require() 函數時,模組會立即加載和執行。這種方式在伺服器端(如 Node.js)非常合適,但在瀏覽器環境中可能導致性能問題,因為瀏覽器需要在模組加載完成後才能繼續執行後續代碼。

    1
    // 導入模組
    2
    const math = require("./math");
    3
    console.log(math.add(2, 3));

ES Module:

  • 靜態和異步加載:ES Module 支援靜態和異步加載。模組的導入在編譯階段靜態分析,這允許 JavaScript 引擎進行優化。ES Module 支援通過 import 語法進行異步加載(使用動態 import()),這在瀏覽器環境中尤為重要。

    1
    // 靜態導入
    2
    import { add } from "./math.js";
    3
    console.log(add(2, 3));
    4
    5
    // 動態導入
    6
    import("./math.js").then((module) => {
    7
    console.log(module.add(2, 3));
    8
    });

2. 模組導出和導入

CommonJS:

  • 模組導出:使用 module.exportsexports 對象來導出模組的功能。可以導出對象、函數或其他值。

    1
    // 導出模組
    2
    module.exports = {
    3
    add: function (a, b) {
    4
    return a + b;
    5
    },
    6
    };
  • 模組導入:使用 require() 函數來導入模組。

    1
    // 導入模組
    2
    const math = require("./math");
    3
    console.log(math.add(2, 3));

ES Module:

  • 模組導出:使用 export 關鍵字來導出模組的功能,支援命名導出和預設導出。

    1
    // 命名導出
    2
    export function add(a, b) {
    3
    return a + b;
    4
    }
    5
    6
    // 預設導出
    7
    export default function add(a, b) {
    8
    return a + b;
    9
    }
  • 模組導入:使用 import 關鍵字來導入模組,支援導入模組的一部分或整個模組。

    1
    // 導入命名導出
    2
    import { add } from "./math.js";
    3
    console.log(add(2, 3));
    4
    5
    // 導入預設導出
    6
    import add from "./math.js";
    7
    console.log(add(2, 3));

3. 模組解析

CommonJS:

  • 動態解析:模組路徑是動態解析的,可以在代碼運行時計算模組路徑。這使得條件導入或動態加載模組變得可能。

    1
    const math = require("./math-" + someCondition + ".js");

ES Module:

  • 靜態解析:模組路徑在編譯階段靜態解析,編譯器可以在代碼運行之前確定依賴關係。這使得靜態分析和優化變得可能,但不支援動態解析路徑。

    1
    import { add } from "./math.js";

4. 兼容性

CommonJS:

  • 主要用於 Node.js:CommonJS 是 Node.js 的預設模組系統,瀏覽器環境不直接支援 CommonJS 模組,但可以通過工具如 Browserify 或 Webpack 來使用 CommonJS 模組。

ES Module:

  • 標準化:ES Module 是 ECMAScript 標準的一部分,現代瀏覽器和 Node.js 都廣泛支援。它是前端和後端都推薦的模組系統。

5. 運行時行為

CommonJS:

  • 動態加載和快取:CommonJS 模組在第一次 require 時加載和執行,隨後快取。對同一個模組的後續 require 調用會返回快取中的模組實例。

ES Module:

  • 靜態加載和環形依賴:ES Module 支援靜態分析,使得模組的依賴關係可以在編譯階段確定。對於環形依賴,ES Module 允許部分加載,這意味著模組在加載時仍然可以引用其他模組的部分內容。

6. 導出值 vs. 導出引用

CommonJS:

  • 導出值是拷貝:CommonJS 中,模組導出的值是導出對象的一個拷貝。修改導出的對象不會影響其他模組中看到的值。

    math.js
    1
    let count = 0;
    2
    3
    module.exports = {
    4
    add: function (a, b) {
    5
    return a + b;
    6
    },
    7
    getCount: function () {
    8
    return count;
    9
    },
    10
    };
    11
    12
    // app.js
    13
    const math = require("./math");
    14
    console.log(math.getCount()); // 輸出: 0
    15
    math.count = 10; // 不會改變 math.getCount() 返回值
    16
    console.log(math.getCount()); // 仍然輸出: 0

ES Module:

  • 導出值是引用:ES Module 中,模組導出的值是對原始對象的引用。對導出對象的修改會在其他模組中反映出來。

    math.js
    1
    export let count = 0;
    2
    3
    export function add(a, b) {
    4
    return a + b;
    5
    }
    6
    7
    // app.js
    8
    import { count, add } from "./math.js";
    9
    console.log(count); // 輸出: 0
    10
    count = 10; // 會改變 math.js 中的 count
    11
    import { count as newCount } from "./math.js";
    12
    console.log(newCount); // 仍然輸出: 10

結論

  • CommonJS 適合伺服器端(Node.js)使用,其模組以同步方式載入,簡單易用,但在瀏覽器環境中需要額外的工具支援。
  • ES Module 是 ECMAScript 的標準模組系統,支持靜態分析和優化,適合現代瀏覽器和 Node.js,具有更好的性能和靈活性。

選擇適合的模組系統可以幫助提高代碼的可維護性和性能,根據專案需求和環境進行選擇是至關重要的。