ES Module 是 ECMAScript 2015 最重要功能,讓 ECMAScript 總算有標準 Module,且語法只有 import 與 export,非常精簡。
Version
macOS Catalina 10.15.1
VS Code 1.40.1
Quokka 1.0.261
ECMAScript 2015
ES Module
可將 data (variable、object、function、class) 加以 import 或 export。
Export 分為 named export 與 default export:
- Named Export:data 必須有名稱
- Default Export:data 沒有名稱 (anonymous object、anonymous function、anonymous class)
- 一個 module 只能有一個 default export,但能有無限多個 named export
Default export 的 data 也可以有名稱,但因為會由 import 決定名稱,所以通常會使 data 沒名稱
Named Export
Variable
export let title = 'FP in JavaScript'
export const price = 100
可直接對 let 與 const 變數加以 export 。
import { title, price } from './my-module'
title // ?
price // ?
可對 variable 分別 import,但 named Import 要搭配 {}。
為什麼可以使用
{}object destructuring 語法呢 ?

let title = 'FP in JavaScript'
let price = 100
export {
title,
price
}
事實上直接對 variable export,相當於使用 property shorthand 先整理成 anonymous object 後才 export。
import { title, price } from './my-module'
title // ?
price // ?
因此 import 才可以使用 {} object destructuring。

Object
export let obj = {
title: 'FP in JavaScript',
price: 100
}
也可直接對 object 加以 export。
import { obj } from './my-module'
obj.title // ?
obj.price // ?
可對 object 直接 import,使用 object destructuring 對 object 解構。

import { title, price } from './my-module'
title // ?
price // ?
直覺會想用 object destructuring 將 property 取出,但事實上並非標準語法。
let obj = {
title: 'FP in JavaScript',
price: 100
}
export {
obj
}
根據剛才經驗,export 會整理成 object。
import {{ title, price }} from './my-module'
title // ?
price // ?
所以應該要兩層 object destructuring 才合乎邏輯,但這種寫法可讀性不高,而且 ES6 也不允許這樣寫。
import { obj } from './my-module'
let { title, price } = obj
title // ?
price // ?
若堅持要使用 object destructuring,應該另外使用 let,而不能在 import 一次解構。

Function
export function add(x, y) {
return x + y
}
將 add() 加以 export。
import { add } from './my-module'
add(1, 1) // ?
對 function 加以 named import 要加上 {}。

export let add = (x, y) => x + y
也可將 arrow function 加以 export。
import { add } from './my-module'
add(1, 1) // ?
對 arrow function 加以 named import 也要加上 {}。

Class
export class Counter {
constructor(x, y) {
this.x = x
this.y = y
}
sum() {
return this.x + this.y
}
}
將 Counter class 加以 export。
import { Counter } from './my-module'
let counter = new Counter(1, 1)
counter.sum() // ?
對 class 加以 named import 要加上 {}。

無論對 variable / object / function / class 加以 named export,都會事先明確命名,然後在 named import 時都加上
{}object destructuring,因為export會先湊成 object 再 export
Default Export
Variable
ES 6 無法對 var、let 與 const 使用 default export,因為並不存在 anonymous variable。
Object
export default {
title: 'FP in JavaScript',
price: 100
}
對於 anonymous object 可使用 default export。
import MyObject from './my-module'
MyObject.title // ?
MyObject.price // ?
由於 anonymous object 本來就沒有名稱,因此 default import 要重新命名。

import { title, price } from './my-module'
title // ?
price // ?
與 named import 一樣,不存在直接在 import 做 object destructuring。
import MyObject from './my-module'
let { title, price } = MyObject
title // ?
price // ?
若堅持要使用 object destructuring,應該另外使用 let,而不能在 import 一次解構。

Function
export default function(x, y) {
return x + y;
}
對於 anonymous function 可使用 default export。
import add from './my-module'
add(1, 1) // ?
由於 anonymous function 本來就沒有名稱,因此 default import 要重新命名。

export default (x, y) => x + y
對於 arrow function 可使用 default export。
import add from './my-module'
add(1, 1) // ?
由於 arrow function 本來就沒有名稱,因此 default import 要重新命名。

Class
export default class {
constructor(x, y) {
this.x = x
this.y = y
}
sum() {
return this.x + this.y
}
}
對於 anonymous class 可使用 default export。
import Counter from './my-module'
let counter = new Counter(1, 1)
counter.sum() // ?
由於 anonymous class 本來就沒有名稱,因此 default import 要重新命名。

無論對 variable / object / function / class 加以 default export,可不用事先明確命名 (當然要事先命名亦可,但沒有太大意義),然後在 named import 時不用加上
{}
Named + Default Export
一個 Module 只允許一個 default export,但可以有多個 named export。
export let title = 'FP in JavaScript'
export let mul = (x, y) => x * y
export default class {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
return this.x + this.y;
}
}
title 與 mul 為 named export 可以多個,但 anonymous class 為 default export只有一個。
import { title, mul } from './my-module'
import Counter from './my-module'
title // ?
mul(1, 2) // ?
let counter = new Counter(1, 1)
counter.sum() // ?
Named export 要搭配 named import,而 default export 則搭配 default export。

Import Entire Module
實務上一個 module 可能有很多 export,要一個一個 import 很辛苦,也可將整個 module 都 import 進來。
import * as MyModule from './my-module'
MyModule.title // ?
MyModule.mul(1, 3) // ?
let counter = new MyModule.default(1, 1)
counter.sum() // ?
對於 named export 沒問題,名稱會維持原來名稱。
但對於沒有名稱的 default export,會以 default 為名稱。

Alias
若對原本 data 名稱覺得不滿意,在 named export 或 named import 時都可以重新取別名。
let add = (x, y) => x + y
export { add as sum }
在 named export 時,可使用 as 將 add 取別名為 sum,但需搭配 {}。
import { sum } from './my-module'
sum(1, 1)
既然已經取別名為 sum,就可以 sum 為名稱 import 進來。

export let add = (x, y) => x + y
直接使用 named export 將 add() export 出來。
import { add as sum } from './my-module'
sum(1, 1) // ?
在 named Import 時才使用 as 取別名亦可。

Conclusion
- ES6 module 分成 named export 與 default export,一個 module 只能有一個 default export,但可以有多個 named export
export與import還可搭配as取別名
Reference
John Resig, Secret of the JavaScript Ninja, 2nd
MDN, export
MDN, import