請啟用 Javascript 查看內容

ES6 Module

 ·  ☕ 4 分鐘

JavaScript筆記 目錄

前言

當專案越來越大時,龐大的程式碼會變得難以離解及維護,因此模組化管理就變得非常重要。模組化就是將程式分解成多個群集,可方便管理維護,不但可以提升程式碼的複用性,更能避免命名上的衝突。

但 JavaScript 早期作為輕量級的腳本語言,只用於在 Web 上與使用者進行簡單的互動,並沒有依賴管理的概念,因此在 ES6 Module 出現前,都未內建支援模組系統。為了解決模組化的問題,JavaScript 社群發展出許多解決方案。

主流的模組化規範為以下兩種:

  • CommonJS,最早是在 Server 架構下設計出來的,最初名為 ServerJS。Node.js 曾經遵循 CommonJS 來實現模組管理,許多開法者將其視為 CommonJS 實作代表。
  • AMD(Asynchronous Module Definition),是一個在瀏覽器端模組化開發的規範。而 RequireJS 為最受歡迎的 AMD 實作。

關於 CommonJS 與 AMD 歷史,可以參考以下文章:

ES6 Module

1. script 標籤

使用 ES6 Module,需要在 <script> 上添加 type="module",否則會拋出錯誤。

1
2

<script type="module" src="js/index.js"/>

type 設置為 "module" 時,預設具有 defer 的特性,.js 檔案會以非同步的方式下載,也就是會在 DOM 解析完畢後執行。

另外,ES6 Module 自動採用嚴格模式。

2. export 和 import

模組功能主要是由 exportimport 關鍵字構成:

  • export 用於匯出
  • import 用於匯入

每個檔案都是一個獨立的模組,模組內部的所有變數都是私有的,其他模組無法訪問。因此若希望外部能夠讀取模組內部的變數、函式或類別,可以用 export 匯出想要公開的名稱。這樣就可以在另一個模組中用 import 匯入使用。

基本範例:

1
2
// moduleA.js
export const a = 'hi';
1
2
3
// index.js
import { a } from './moduleA.js';
console.log(a); // "hi"
exportimport 只能出現在頂層作用域,它們必須出現在所有區塊與函式之外。舉例來說,你無法使用 if 條件式將 exportimport 包起來。

export

每個模組中可以使用多個 export

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// moduleA.js
export const a = 'hi!';
export function foo() {
  // ...
}
export class User {
  constructor(name) {
    // ...
  }
}

除了上述的直接匯出,也可以先宣告,並使用大括號將要匯出的名稱包起來。:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// moduleA.js
const a = 'hi';
function foo() {
  // ...
}
class User {
  constructor() {
    // ...
  }
}

export { a, foo, User };

兩種寫法是等價的,但它可以很清楚這個模組對外公開的名稱。

export 必須與模組內部的變數建立對應的關係,否則會報錯。
1
2
3
4
5
6
// 錯誤範例 1
export 'hi';

// 錯誤範例 2
const a = 'hi';
export a;

export 匯出的名稱可以使用 as 關鍵字重新命名:

1
2
3
4
5
6
7
8
// moduleA.js
const a = 'hi';
function foo() {}

export { 
  a as newA,
  foo as newFoo
};
1
2
3
// index.js
import { newA, newFoo } from './moduleA.js';
console.log(newA); // "hi"

import

import 一樣可以使用 as 關鍵字,將匯入的名稱重新命名:

1
2
3
// index.js
import { a as newA } from './moduleA.js';
console.log(newA); // "hi"

假設要一次匯入所有模組匯出內容,可以使用 * as

1
2
3
4
// index.js
import * as moduleA from './moduleA.js';

console.log(moduleA.a); // "hi"
import 指定的名稱,相當於 const 宣告,因此無法在匯入的檔案內改寫。
1
2
3
// index.js
import { a } from './moduleA.js';
a = 'hello'; // 報錯
如果是該變數是物件,因為傳址特性改寫屬性是允許的,但這種操作很難除錯,因此不建議這樣操作。

import 具有提升(Hoisting)效果:

1
2
3
// index.js
console.log(newA); // "hi"
import { a } from './moduleA.js';

若該模組沒有任何值需要匯出,僅執行某些功能, import 路徑即可:

1
2
3
4
5
6
// index.js
function foo() {
  console.log('hi');
}

foo();
1
2
3
// index.js
import './moduleA.js';
// "hi"

export default

export 屬於 具名匯出(named export) 的用法,模組就算公開的名稱只有一個,也必須定義名稱:

1
2
3
// moduleA.js
const a = 'hi';
export { a };

使用 import 時,還需要查閱公開名稱。

而匯出還有一種 預設匯出(default export),使用 export default 關鍵字,可以直接匯出不需要定義名稱。

1
2
3
4
5
6
7
8
// 函式
export default function () {};

// 或者物件
export default {};

// 或值
export default 'hi';
但要注意,模組內只能有一個 export default

一樣可以先宣告,但不需要 {}

1
2
3
// moduleA.js
const a = 'hi';
export default a;

使用 import 匯入時,可以任意指定名稱,不需要 {}。:

1
2
// index.js
import a from './moduleA.js';

exportexport default 可以同時存在:

1
2
3
// moduleA.js
export const a = 'hi';
export default 'hello';
1
2
3
4
5
6
// index.js
import { a } from './moduleA.js';
import b from './moduleA.js';

// 或者合併匯入,預設一定要在前
import b, { a } from './moduleA.js';

合併使用

如果模組作為入口,可能會先後輸出同一個模組。

1
2
3
4
// moduleA.js
export const a = 'hi';
// moduleB.js
export const b = 'hello';
1
2
3
4
5
// index.js
import { a } from './moduleA.js';
import { b } from './moduleB.js';

export { a, b };

你可以將 importexport 寫在一起:

1
2
3
4
5
6
7
// index.js
export { a } from './moduleA.js';
export { b } from './moduleB.js';

// or 全部匯出
export * from './moduleA.js';
export * from './moduleB.js';

竹白
作者
竹白
前端筆記

文章目錄