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
模組功能主要是由 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"
 | 
 
  export 與 import 只能出現在頂層作用域,它們必須出現在所有區塊與函式之外。舉例來說,你無法使用 if 條件式將 export 或 import 包起來。
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';
 | 
 
export 和 export 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 };
 | 
 
你可以將 import 和 export 寫在一起:
| 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';
 |