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';
|