深入ES6 子產品系統
本文轉載自: 衆成翻譯 譯者: neck 連結: http://www.zcfy.cc/article/4436 原文: https://ponyfoo.com/articles/es6-modules-in-depth#the-es6-module-system
ES6 子產品系統
在ES6之前,我們用自己的方式來在 JavaScript 中實作子產品。很長一段時間以來,像 RequireJS、Angular 的依賴注入和 CommonJS 這樣的系統,配合着一些有用的工具,比如 Browserify 和 Webpack,一直在解決我們的需求。然而,到了2015 年,一個标準的子產品系統早就應該釋出了。我們馬上就會看到,你很快會注意到 ES6 子產品受到了 CommonJS 的很大影響。我們将檢視
export
和
import
語句,從中會看到ES6子產品和CommonJS有多一緻,同時,我們将會在這篇文章中讨論它們。
今天我們将介紹 ES6 子產品系統的幾個方面。
- [嚴格模式](# 嚴格模式)
- export
export
- [
最佳實踐](#export
最佳實踐)export
- import
import
- 導入預設的Exports
- [導入具名的 Exports](#導入具名的 Exports)
-
所有内容](#import
所有内容)import
嚴格模式
在 ES6 子產品系統中, 嚴格模式預設被開啟。如果你不知道嚴格模式是什麼, 它隻是語言的一個更嚴格的版本它讓語言的很多不好的部分都消失了。它使編譯器可以通過在使用者代碼中禁止使用一些不可靠的文法來表現得更好。下面是對 MDN 上的
嚴格模式文章中所記錄的更改的總結。
- 變量不能未聲明就使用
- 函數參數必須有唯一的名稱 (否則會被認為是文法錯誤)
-
語句被禁止使用with
- 指派給隻讀屬性會抛出一個錯誤
- 像
這樣的八進制數是文法錯誤00840
- 嘗試
不可删除的資料會抛出一個錯誤delete
-
被認為是文法錯誤, 隻能删除屬性delete prop
delete global[prop]
-
不會引入新的變量到它的作用域eval
-
eval
的綁定不會被改變arguments
-
不會神奇地跟蹤方法參數的變化arguments
- 不再支援
,使用它會抛出arguments.callee
TypeError
-
arguments.caller
TypeError
- 上下文作為
在方法調用時不會被強制包裝成一個this
(譯者注:即this不會指向全局對象)Object
- 不再能夠使用
andfn.caller
通路 JavaScript 的堆棧fn.arguments
- 保留字(例如
,protected
static
等等)不能被作為新變量聲明interface
如果這些規則對你來說不是顯而易見的,你應該使用
'use strict'
在每一個地方。盡管在 ES6 中已經成為事實,但在 ES6 中使用
'use strict'
仍然是一種很好的做法。我已經使用嚴格模式很長時間了,并且絕不會用回原來的模式!
現在讓我們了解
export
,我們的第一個 ES6 子產品關鍵字!
export
export
在 CommonJS 中,你将值暴露在
module.exports
上來導出它們。正如下面的代碼片段所示,您可以導出任何内容像是基本類型、對象、數組或函數。
module.exports = 1
module.exports = NaN
module.exports = 'foo'
module.exports = { foo: 'bar' }
module.exports = ['foo', 'bar']
module.exports = function foo () {}
ES6子產品系統将
export
封裝成API,類似于 CommonJS的
modules
。ES6 子產品中的聲明隻作用于該子產品,和使用 CommonJS 一樣。這意味着,在子產品中聲明的任何變量都不能用于其他子產品,除非它們明确地導出為子產品 API 的一部分(然後導入到希望通路它們的子產品中)。
你可以通過把
module.exports =
變成
export default
來模拟我們剛剛看到的CommonJS代碼。
export default 1
export default NaN
export default 'foo'
export default { foo: 'bar' }
export default ['foo', 'bar']
export default function foo () {}
與 CommonJS 不同,導出語句隻能放在 ES6 子產品的最外層,而不能放在方法中,即使在加載子產品時它們所在的方法會立即被調用。據推測,這種限制是為了讓編譯器更容易地解釋 ES6 子產品,但是這也是一個很好的限制,因為有很多很好的理由去以動态地定義和暴露 API的方式來調用方法。
function foo () {
export default 'bar' // SyntaxError
}
foo()
你不隻可以使用預設的Export,你還可以使用具名的Exports。
在 CommonJS 中,你甚至不需要事先配置設定一個對象給
module.exports
。你可以把屬性添加到它上面。不管
module.exports
最終的屬性包含什麼,它仍然是一個單獨的綁定。
module.exports.foo = 'bar'
module.exports.baz = 'ponyfoo'
我們可以通過使用具名導出文法在 ES6 子產品中複制上述内容,而不是像CommonJS一樣将它配置設定給
module.exports
。在ES6中,你可以聲明要
export
的綁定。注意,下面的代碼不能重構為先聲明變量再執行
export foo
,那将會導緻一個文法錯誤。在這裡,我們看到了ES6子產品如何通過聲明式子產品系統API的工作方式來支援靜态分析。
export var foo = 'bar'
export var baz = 'ponyfoo'
還有一個重要的點,是要記住我們正在導出的是綁定。
重要的一點是,ES6 子產品導出的是綁定,而不是值或引用。這意味着您導出的
foo
變量将被綁定到子產品上的
foo
變量中,它的值将取決于對
foo
的修改。 不過,我建議在最初加載子產品之後,不要更改子產品的公共接口。
如果你有一個
./a
子產品像下面這樣,這導出的
foo
将被綁定為
'bar'
,持續500ms之後,
foo
将綁定為
'baz'
export var foo = 'bar'
setTimeout(() => foo = 'baz', 500)
除了預設綁定和單獨綁定之外,你還可以導出一個綁定清單。
綁定清單
正如下面的代碼片段所示,ES6 子產品允許你導出已命名的位于頂級作用域的成員清單。
var foo = 'ponyfoo'
var bar = 'baz'
export { foo, bar }
如果你想要用其他名字來導出一個綁定,你可以使用
export { foo as bar }
語句,就像下面展示的這樣。
`export { foo as ponyfoo }`
在使用
export
的命名成員清單聲明風格時,還可以使用
as default
。下面代碼的作用和執行
export default foo
export bar
一樣,隻不過在一行語句而已。
`export { foo as default, bar }`
隻在子產品檔案的底部使用
export default
有很多好處。
export
最佳實踐
export
可以定義具名的Exports,可以導出一個具有别名的清單,還可以暴露一個預設的
export
,這會導緻一些混亂。在很大程度上,我鼓勵你們使用
export default
并且最好在子產品檔案的末尾使用。如下代碼所示,你可以調用你的API 對象
api
或者将它命名為子產品本身。
var api = {
foo: 'bar',
baz: 'ponyfoo'
}
export default api
第一,子產品的導出接口立即變得明顯。無需在子產品中翻查并将各個部分組合在一起來計算 API,您隻需滾動到最後。有一個清晰定義的 API 導出的地方,也可以更容易地解釋子產品導出的方法和屬性。
第二,是應該使用
export default
還是具名的導出又或者是清單的導出甚至是帶有别名的導出,你不應該糾結這個。現在有一個指導方針,就是在任何地方都使用
export default
。
第三,一緻性。 在CommonJS世界中,我們通常從子產品中導出一個方法,然後就可以了。而使用具名導出進行這樣的操作是不可能的,因為你暴露了一個對象來表示該方法,除非你在導出清單中使用
as default
。
第四,這實際上是之前所提到的點的總結。
export default
語句放在子產品的底部,我們立即可以很清晰的看出這個子產品的API是什麼、有哪些方法,可以讓子產品的使用者可以很輕松的調用它的 API。當習慣于使用
export default
并總是在子產品的最後使用它,你會感到使用ES6的子產品系統是無痛的。
現在我們已經讨論了
export
API 及其注意事項,讓我們開始讨論
import
語句。
import
import
這個語句是和
export
相對的語句。首先,它們可以被用來從另一個子產品加載一個子產品,這種加載子產品的方式是特别實作的,目前還沒有浏覽器實作子產品加載。聰明的人會在浏覽器中解決子產品加載問題,這樣,你就可以立即編寫符合标準的 ES6 代碼。像 Babel 這樣的轉換工具可以在子產品系統的幫助下像CommonJS一樣連接配接子產品。意味着在babel中,
import
語句和CommonJS中的
require
語句遵循一樣的語義。
讓我們以
lodash
為例。下面的語句簡單地從子產品中加載 Lodash 子產品。它并沒有建立任何變量,但它将可以使用
lodash
子產品。
`import 'lodash'`
在導入綁定之前,讓我們來關注一下
import
語句的實際情況。和
export
很像,它隻能定義在子產品的頂級作用域。這可以幫助轉換工具實作它們的子產品加載功能,并幫助其它靜态分析工具解析你的代碼庫。
在CommonJS中,你可以通過
require
語句
import
一些代碼,就像這樣:
`var _ = require('lodash')`
要從ES6子產品導入預設的導出綁定,你隻需要為它指定一個名字。與聲明一個變量相比,文法有點不同,因為你正在導入一個綁定,而且可以讓它更利于靜态分析工具的分析。
`import _ from 'lodash'`
你也可以導入具名的導出并且可以使用别名。
導入具名的導出
這裡的文法和我們剛才使用的預設導出非常相似,隻需添加一些大括号,然後選擇任意指定的導出. 注意,這個文法類似于
解構指派文法,但也有一些不同。
`import {map, reduce} from 'lodash'`
不同于解構指派的是,你可以使用别名來重命名導入的綁定。你可以在你認為合适的情況下混合使用别名和非别名的導出。
`import {cloneDeep as clone, map} from 'lodash'`
你還可以混合和比對指定的導出和預設導出。如果你想要它在括号裡,你必須使用
default
的名稱,你可以為
default
指定别名;或者你也可以将預設的導入與指定的導入清單混合在一起。
import {default, map} from 'lodash'
import {default as _, map} from 'lodash'
import _, {map} from 'lodash'
最後,還有
import *
的語句
import
所有内容
import
你還可以将一個子產品導入為命名空間對象。它不導入指定的導出或預設值,而是導入所有的東西。注意,導入文法必須使用别名,其中所有綁定都将被替換到别名上。如果有一個預設的導出,将會被替換為
alias.default
。
`import * as _ from 'lodash'`
上面的代碼展示了這個文法。
結論
注意,你可以在利用CommonJS子產品的同時,通過babel編譯器來使用ES6子產品。最重要的是,你可以在CommonJS和ES6子產品之間進行互操作。這意味着即使你導入了一個用CommonJs編寫的子產品,它也會起作用。
ES6子產品系統看起來很棒,它是JavaScript中缺少的最重要的東西之一。我希望他們能很快找到一個最終完成的子產品加載API和浏覽器實作。你可以從一個子產品中
export
或
import
綁定的多種方法,但這并不多,因為它們增加了複雜性,但是時間将會告訴你,所有額外的API是否和它的龐大一樣友善。