模組入門


JavaScript 在模組的發展上,有著一段混亂的歷史,正如〈名稱空間管理〉中看到的,現在有 CommonJS、AMD 等模組標準,然而,亦存在著各式的變體,這使得不同標準之間的模組若要互相合作,存在著一定的困難度。

ES6 納入了模組規範,就在於試圖解決這類的問題,正如同 ES6 納入類別語法,在可以用它解決需求的情況下,應該採用以增加互通性,面對 ES6 的模組方案也是如此,在可以用它解決模組需求的情況下,當然是儘可能使用。

然而,在前端或後端,對於 ES6 模組的支援相對來說,是比較慢的,以 Node.js 來說,目前處於實驗階段,在 8.5 之後,必須使用 --experimental-modules 打開才能使用。

對於 ES6 模組來說,一個 .js 是一個模組檔案,然而由於 ES6 模組與原本 Node.js 的模組有在載入機制與靜態分析上並不相同,Node.js 必須有方式可以區分,這是 Node.js 既有的模組,或者是 ES6 的模組,因而對於 ES6 模組,暫時使用了個 .mjs 副檔名作為區別。

在瀏覽器上,要載入 ES6 模組,同樣是透過 <script> 標籤,然而 type 屬性的值是 "module",這讓瀏覽器知道這會是個 ES6 模組,這之後在談到瀏覽器上的 JavaScript 操作時會再說明。

總之,一個 .js 檔案是一個 ES6 模組,在當中所有的名稱,作用範圍都侷促在 .js 之中,想要可以被使用的名稱,可以使用 export 來公開,例如,定義一個 math.js 作為模組:

function max(a, b) {
    return a > b ? a : b;
}

function min(a, b) {
    return a < b ? a : b;
}

function sum(...numbers) {
    return numbers.reduce((acc, value) => acc + value);
}

const PI = 3.141592653589793;
const E = 2.718281828459045;

let foo = 'foo';

export {max, min, sum, PI, E};

就這個 math 模組來說,將來其他模組可以使用的,是 maxminsumPIE 這些名稱,foo 名稱沒有 export,它僅在 math 中可用,是 math 模組的私有變數。

如果打算在另一個模組中使用 math 模組,可以使用 import from,就上面的模組定義來說,你必須知道 export 的名稱是什麼,然後指定 import 哪些名稱:

import {max, sum, PI} from './math';

console.log(max(10, 5));          // 10
console.log(sum(1, 2, 3, 4, 5));  // 15
console.log(PI);                  // 3.141592653589793

雖然 math 模組中 export 了五個名稱,然而,只有被 import 至目前模組的名稱才能使用,若必要,也可以為被 import 的名稱取個別名:

import {max as maximum} from './math';

ES6 希望只有真正需要的名稱,才 import 至目前的模組成為該模組中的名稱,如果想一次從模組中 importexport 的全部名稱,必須有個前置名稱參考至一個物件,而被 import 的名稱,都會是該物件上的特性:

import * as math from './math';

console.log(math.max(10, 5));
console.log(math.sum(1, 2, 3, 4, 5));
console.log(math.PI);

一個模組也可以在定義名稱時,同時進行 export

export function max(a, b) {
    return a > b ? a : b;
}

export function min(a, b) {
    return a < b ? a : b;
}

export function sum(...numbers) {
    return numbers.reduce((acc, value) => acc + value);
}

export const PI = 3.141592653589793;
export const E = 2.718281828459045;

export 時,也可以為名稱取別名再 export

function max(a, b) {
    return a > b ? a : b;
}

function min(a, b) {
    return a < b ? a : b;
}

function sum(...numbers) {
    return numbers.reduce((acc, value) => acc + value);
}

const PI = 3.141592653589793;
const E = 2.718281828459045;

export {max as maximum, min as minimum, sum, PI, E};

import 的名稱,無論是否宣告為 const,都是不可變動(Immutable),試圖重新指定值給它,會引發 TypeError

import {maximum, minimum} from './math';

maximum = function() {};  // TypeError: Assignment to constant variable.

ES6 的模組是靜態的,importexport 必須是在模組的頂層,也就是說,你不能在 if..else 或者是函式中放 importexport,因為靜態分析時並不執行程式碼。