深刻ES6 模塊系統

深刻ES6 模塊系統

本文轉載自:衆成翻譯
譯者:neck
連接:http://www.zcfy.cc/article/4436
原文:https://ponyfoo.com/articles/es6-modules-in-depth#the-es6-module-systemes6

ES6 模塊系統

在ES6以前,咱們用本身的方式來在 JavaScript 中實現模塊。很長一段時間以來,像 RequireJS、Angular 的依賴注入和 CommonJS 這樣的系統,配合着一些有用的工具,好比 Browserify 和 Webpack,一直在解決咱們的需求。然而,到了2015 年,一個標準的模塊系統早就應該發佈了。咱們立刻就會看到,你很快會注意到 ES6 模塊受到了 CommonJS 的很大影響。咱們將查看exportimport語句,從中會看到ES6模塊和CommonJS有多一致,同時,咱們將會在這篇文章中討論它們。api

今天咱們將介紹 ES6 模塊系統的幾個方面。數組

嚴格模式

在 ES6 模塊系統中, 嚴格模式默認被開啓。若是你不知道嚴格模式是什麼, 它只是語言的一個更嚴格的版本它讓語言的不少很差的部分都消失了。它使編譯器能夠經過在用戶代碼中禁止使用一些不可靠的語法來表現得更好。下面是對 MDN 上的嚴格模式文章中所記錄的更改的總結。

  • 變量不能未聲明就使用

  • 函數參數必須有惟一的名稱 (不然會被認爲是語法錯誤)

  • with 語句被禁止使用

  • 賦值給只讀屬性會拋出一個錯誤

  • 00840 這樣的八進制數是語法錯誤

  • 嘗試 delete 不可刪除的數據會拋出一個錯誤

  • delete prop 被認爲是語法錯誤, 只能刪除屬性 delete global[prop]

  • eval 不會引入新的變量到它的做用域

  • evalarguments 的綁定不會被改變

  • arguments 不會神奇地跟蹤方法參數的變化

  • 再也不支持arguments.callee,使用它會拋出 TypeError

  • 再也不支持arguments.caller,使用它會拋出 TypeError

  • 上下文做爲 this 在方法調用時不會被強制包裝成一個 Object(譯者注:即this不會指向全局對象)

  • 再也不可以使用 fn.caller and fn.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 fooexport 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和瀏覽器實現。你能夠從一個模塊中exportimport綁定的多種方法,但這並很少,由於它們增長了複雜性,可是時間將會告訴你,全部額外的API是否和它的龐大同樣方便。

相關文章
相關標籤/搜索