點燈坊

學而時習之,不亦悅乎

ECMAScript 之 ES Module

Sam Xiao's Avatar 2019-11-29

ES Module 是 ECMAScript 2015 最重要功能,讓 ECMAScript 總算有標準 Module,且語法只有 importexport,非常精簡。

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

可直接對 letconst 變數加以 export 。

import { title, price } from './my-module'

title // ?
price // ?

可對 variable 分別 import,但 named Import 要搭配 {}

為什麼可以使用 {} object destructuring 語法呢 ?

module000

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。

module001

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 解構。

module002

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 一次解構。

module003

Function

export function add(x, y) {
  return x + y
}

add() 加以 export。

import { add } from './my-module'

add(1, 1) // ?

對 function 加以 named import 要加上 {}

module004

export let add = (x, y) => x + y

也可將 arrow function 加以 export。

import { add } from './my-module'

add(1, 1) // ?

對 arrow function 加以 named import 也要加上 {}

module005

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 要加上 {}

module006

無論對 variable / object / function / class 加以 named export,都會事先明確命名,然後在 named import 時都加上 {} object destructuring,因為 export 會先湊成 object 再 export

Default Export

Variable

ES 6 無法對 varletconst 使用 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 要重新命名。

module007

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 一次解構。

module008

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 要重新命名。

module009

export default (x, y) => x + y

對於 arrow function 可使用 default export。

import add from './my-module'

add(1, 1) // ?

由於 arrow function 本來就沒有名稱,因此 default import 要重新命名。

module010

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 要重新命名。

module015

無論對 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;
  }
}

titlemul 為 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。

module011

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 為名稱。

module012

Alias

若對原本 data 名稱覺得不滿意,在 named export 或 named import 時都可以重新取別名。

let add = (x, y) => x + y

export { add as sum }

在 named export 時,可使用 asadd 取別名為 sum,但需搭配 {}

import { sum } from './my-module'

sum(1, 1)

既然已經取別名為 sum,就可以 sum 為名稱 import 進來。

module013

export let add = (x, y) => x + y

直接使用 named export 將 add() export 出來。

import { add as sum } from './my-module'

sum(1, 1) // ?

在 named Import 時才使用 as 取別名亦可。

module014

Conclusion

  • ES6 module 分成 named export 與 default export,一個 module 只能有一個 default export,但可以有多個 named export
  • exportimport 還可搭配 as 取別名

Reference

John Resig, Secret of the JavaScript Ninja, 2nd
MDN, export
MDN, import