ES6特性:Module模塊化

ES6: Module 模塊化

簡介

在 ES6 之前,JS 語言並不具備模塊(Module)的特性,隨著系統規模和代碼量急劇的增加,模塊化勢在必行。一開始出現了如 CommonJS、AMD 等標準,一直到 ES6 才出現統一標準化的模塊化語法,有了 Module 就能夠實現代碼分離,並且為動態加載代碼鋪路,接下來就讓我們來瞧瞧 ES6 標準裡的模塊化語法。

參考

ES6 modulehttp://caibaojian.com/es6/module.html

正文

Usage 應用

由於部分瀏覽器版本可能不支持模塊化系統(較新版的應該都沒問題),依需求可以使用 babel 進行轉換,可在我的 Babel 入門找到簡單的使用介紹,並使用 node 環境執行代碼,這邊主要介紹語法。

Overview 概述

模塊化的語法最主要就是兩個關鍵字:exportimport。一個導出,一個導入,簡單明瞭。

export 導出

ES6 的模塊系統,將一個 JS 文件視為一個模塊,開發者就可以使用 export 向模塊外部暴露(導出)變量或是方法,最基本的用法如下:

export var 個別導出
// a.js
export const a = 1
export const o = { value: 2 }
export const f = function () {
  console.log('invoke f')
}
export class C {}

// b.js
import { a, o, f, C } from './a'

這邊使用 { a, o, f, C } 表示從 a 模塊中導入 aofC 三個變量,後續會介紹 import 相關語法。

export {} 一組導出

除了像上面一樣一個個導出變量或方法之外,還可以使用 export {} 來一次導出一組變量,如下:

// a.js
const a = 1
const o = { value: 2 }
const f = function () {
  console.log('invoke f')
}
class C {}

export { a, o, f, C }

// b.js
import { a, o, f, C } from './a'

**注意!**這裏與 CommonJS 不同,export 後面的 {} 並不是一個對象,所以並不是 { a:a } 的縮寫,也不能這樣使用,別名需要使用後面的 as 語法。

將多個變量個別導出,與一次導出一組變量是等價的,如下:

export const a = 1
export const b = 2
export const c = 3
// 等價於
const a = 1
const b = 2
const c = 3
export { a, b, c }
export { as } 導出別名

能夠導出變量之後,我們可能希望裡外的命名空間能夠區別開來,這時候就可以使用關鍵字 as 來改變對外暴露的接口:

// a.js
const hello = function () {
  console.log('hello')
}
export { hello as hi }

// b.js
import { hi } from './a'
  • 說明:由於導出時為 hello 取了別名 hi,因此其他模塊引用的時候要使用 hi,hello 將被視作未定義
export default 默認導出

有時候使用者並不知道模塊內的變量或方法的確切名字,相反的我們希望使用者只使用模塊導出的唯一值,那們我們就可以使用 export default 語法而不需要為其指定一個名字:

// a.js
export default {
  el: '#app',
  name: 'default object'
}

// b.js
import A from './a'
  • 說明 1:注意這邊的 export default {},由於 export default 本身也是導出某個值,所以這裡的 {} 確實是指一個對象,與 export {} 表示導出一組值不同

  • 說明 2:這時候導入就不需要使用 {},後面的 import 會介紹。

其實默認(default)導出是一種別名,如下代碼:

export default const a = 1
// 等價於
export { a as default }
導出引用

這邊需要注意的一點是,模塊導出的是變量的引用,而不是靜態的副本,也就是說導入的變量值實際指向存在於外部模塊的變量,是會動態修改值得,看代碼:

// a.js
export let i = 1

export const inc = function () {
  i++
}

// b.js
import { i, inc } from './a'

console.log(`i = ${i}`)
inc()
console.log(`i = ${i}`)

// output:
// i = 1
// i = 2
  • 說明:由於導入進來的 i 變量指向 a.js 中的變量,因此調用 inc 函數後修改了 i 的值,所以第二次打印的時候 i 的值為 2。

到此,我們已經具備多種手段導出模塊內(單個 JS 文件)的各種變量或方法了,接下來我們來介紹如何導入模塊

import 導入

“不要重複造輪子”,很多時候我們都需要引用別人寫好的模塊,甚至自己的項目也需要模塊化,這時候就需要向外部模塊導入(import)各樣的變量或方法

import {} 個別導入

一個模塊可能對外暴露多個接口(可能使用 export 一個個導出,或是使用 export {} 一次導出多個),從外部模塊的角度來說 就是一組接口,因此在 {} 填上需要使用到的變量名(與 ES6 的對象解構賦值相似但是不同

// a.js
export const a = 1
export const b = 2

// b.js
import { a, b } from './a'

console.log(`a = ${a}`)
console.log(`b = ${b}`)

// output:
// a = 1
// b = 2
import { as } 導入別名

當導入的變量與當前模塊的命名空間產生衝突時,可以使用別名來改變導入變量的名稱,以避免衝突:

// a.js
export let i = 1
export const inc = () => i++

// b.js
import { i as a_i, inc } from './a'
const i = 'i in b.js'
console.log(`i = ${a_i}`)
inc()
console.log(`i = ${a_i}`)
  • 說明:這邊 b 模塊內部已經佔用名字 i 了,因此從 a 模塊導入的變量必須使用別名 a_i 以避免命名空間衝突
import * as 整體導入

有時候我們並不想一一列出導入的變量名,我們可以使用 * 來表示導入整個模塊,並透過 as 為整個模塊取一個別名,這樣就能夠實現一次性導入整個模塊:

// a.js
export let i = 1
export const inc = () => i++

// b.js
import * as A from './a'
console.log(`i = ${A.i}`)
A.inc()
console.log(`i = ${A.i}`)
import 默認導入

這邊對應 export default 的默認導出,當使用默認導出的時候,就可以將模塊本身也代表著某個變量(默認導出的變量),這時候就不需要指定任何名稱也不需要使用 {} 語法了:

// a.js
export default {
  el: '#app',
  name: 'componentA'
}

// b.js
import ComponentA from './a'
console.log(ComponentA)

// output:
// { el: '#app', name: 'componentA' }

export + default 複合寫法

有時候有些模塊作為統一向外暴露的接口,並不需要對導入的模塊的變量做任何處理直接導出,你可能會想到這麼寫:

import A from './a'
export { A }

ES6 提供了一種複合語法,你也可以說是語法糖吧,就是導入同時導出,如下:

// a.js
export const a = 'default a'

// b.js
export * from './a'

// c.js
import { a } from './b'

如果你是要導出一組值則可以這樣寫:

// a.js
export const a = 1
export const b = 2
export const c = 3

// b.js
export { a, b, c } from './a'

// c.js
import { a, b, c } from './a'

這邊如果你想要導出默認路由的話寫 export default 會變成僅僅只是默認導出而不是導入默認模塊,要改變成如下形式:

// a.js
export default 123

// b.js
export { default } from './a'

// c.js
import B from './b'

或是改變默認導出

// a.js
export const a = 123

// b.js
export { a as default } from './a'

// c.js
import B from './b'
提案:簡化先導入再導出

現行的複合語法是先導入再導出,因此會出現下面這種有點憋腳的寫法:

export { a } from './a'

因此 ES7 有一種提案是拿掉輸出後的大括號:

export a from './a'
  • 說明:而默認導入則能夠使用 default 作為替代,寫法上更加簡潔可讀。

import() 動態導入

上述提到的 import 屬於靜態導入,也就是說會先於所有表達式執行,如下代碼:

// a.js
console.log('load a.js')
export default 1

// b.js
console.log('load b.js')
export default 2

// c.js
console.log('load c.js')
import A from './a'
console.log(A)
import B from './b'
console.log(B)

// output:
// load a.js
// load b.js
// load c.js
// 1
// 2

同時如果 import 語句放在非底層代碼塊的時候將會報錯:

if (true) {
  import A from './a'
}

因此就有一個提案說到,應該引入一個動態加載,不僅可以通過條件控制來決定動態引入哪些模塊,同時還能實現懶加載(lazy-import),將真正的依賴引入延遲到運行時,能夠大幅減少引入大量大體積依賴的開銷,提案中的語法如下,import() 函數將會返回一個 Promise 對象:

語法
import('./a').then((A) => {
  // ...
})

這邊的動態加載與 node 的 require 也有不同,提案中的 import() 屬於異步加載,而 node 的 require 則屬於同步加載。

結語

本篇簡單介紹了 ES6 標準下的模塊化規範,如果你看到舊版代碼與 ES6 的語法不同,可能是 ES5 以前的模塊化規範(CommonJS、AMD 等)。一個有良好設計架構並且採用模塊化的技術,可以說是構建一個大型系統的基本要素,即便不是構建大型系統,模塊化依舊能為代碼風格以至體系結構帶來巨大的好處,也是身為一個開發者必須要具備的素養。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值