深入ES6 模塊系統 本文轉載自: "眾成翻譯" 譯者: "neck" 鏈接: "http://www.zcfy.cc/article/4436" 原文: "https://ponyfoo.com/articles/es6 modules in depth the es6 module system ...
深入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 模塊系統的幾個方面。
嚴格模式
在 ES6 模塊系統中, 嚴格模式預設被開啟。如果你不知道嚴格模式是什麼, 它只是語言的一個更嚴格的版本它讓語言的很多不好的部分都消失了。它使編譯器可以通過在用戶代碼中禁止使用一些不可靠的語法來表現得更好。下麵是對 MDN 上的嚴格模式文章中所記錄的更改的總結。
變數不能未聲明就使用
函數參數必須有唯一的名稱 (否則會被認為是語法錯誤)
with
語句被禁止使用賦值給只讀屬性會拋出一個錯誤
像
00840
這樣的八進位數是語法錯誤嘗試
delete
不可刪除的數據會拋出一個錯誤delete prop
被認為是語法錯誤, 只能刪除屬性delete global[prop]
eval
不會引入新的變數到它的作用域eval
和arguments
的綁定不會被改變arguments
不會神奇地跟蹤方法參數的變化不再支持
arguments.callee
,使用它會拋出TypeError
不再支持
arguments.caller
,使用它會拋出TypeError
上下文作為
this
在方法調用時不會被強制包裝成一個Object
(譯者註:即this不會指向全局對象)不再能夠使用
fn.caller
andfn.arguments
訪問 JavaScript 的堆棧保留字(例如
protected
,static
,interface
等等)不能被作為新變數聲明
如果這些規則對你來說不是顯而易見的,你應該使用 'use strict'
在每一個地方。儘管在 ES6 中已經成為事實,但在 ES6 中使用 'use strict'
仍然是一種很好的做法。我已經使用嚴格模式很長時間了,並且絕不會用回原來的模式!
現在讓我們瞭解export
,我們的第一個 ES6 模塊關鍵字!
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。
具名的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
最佳實踐
可以定義具名的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
這個語句是和export
相對的語句。首先,它們可以被用來從另一個模塊載入一個模塊,這種載入模塊的方式是特別實現的,目前還沒有瀏覽器實現模塊載入。聰明的人會在瀏覽器中解決模塊載入問題,這樣,你就可以立即編寫符合標準的 ES6 代碼。像 Babel 這樣的轉換工具可以在模塊系統的幫助下像CommonJS一樣連接模塊。意味著在babel中,import
語句和CommonJS中的require
語句遵循一樣的語義。
讓我們以 lodash
為例。下麵的語句簡單地從模塊中載入 Lodash 模塊。它並沒有創建任何變數,但它將可以使用lodash
模塊。
`import 'lodash'`
在導入綁定之前,讓我們來關註一下import
語句的實際情況。和export
很像,它只能定義在模塊的頂級作用域。這可以幫助轉換工具實現它們的模塊載入功能,並幫助其它靜態分析工具解析你的代碼庫。
導入預設的Exports
在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
所有內容
你還可以將一個模塊導入為命名空間對象。它不導入指定的導出或預設值,而是導入所有的東西。註意,導入語法必須使用別名,其中所有綁定都將被替換到別名上。如果有一個預設的導出,將會被替換為alias.default
。
`import * as _ from 'lodash'`
上面的代碼展示了這個語法。
結論
註意,你可以在利用CommonJS模塊的同時,通過babel編譯器來使用ES6模塊。最重要的是,你可以在CommonJS和ES6模塊之間進行互操作。這意味著即使你導入了一個用CommonJs編寫的模塊,它也會起作用。
ES6模塊系統看起來很棒,它是JavaScript中缺少的最重要的東西之一。我希望他們能很快找到一個最終完成的模塊載入API和瀏覽器實現。你可以從一個模塊中export
或import
綁定的多種方法,但這並不多,因為它們增加了複雜性,但是時間將會告訴你,所有額外的API是否和它的龐大一樣方便。