簡介: 2014年7月底,TC39又召開了一次會議,最後敲定了ECMAScript 6 (ES6)模塊語法的最後細節。本文概述了完整的ES6模塊系統。
一、當前的模塊系統
javaScript沒有內置對模塊的支持,可是社區爲此建立了使人滿意的變通方法。 而這就要說到下面兩條重要的標準:
一、簡潔的語法
二、爲同步加載而設計的,主要是用於服務器端。規範加載模塊是同步的,也就是說,只有加載完成,才能執行後面的操做
- Asynchronous Module Definition (AMD):
一、Slightly more complicated syntax, enabling AMD to work without eval() (or a compilation step).(稍微複雜一點的語法,使AMD能夠在沒有eval()(或編譯步驟)的狀況下工做。這個沒太懂,大概是指語法會複雜一點,可是在未變異狀況下能夠工做,我去搜一下AMD的相關資料,找到一句話,不知道是否是匹配這裏。
AMD同時是「匿名的」,意味着模塊不須要硬編碼指向其路徑的引用, 模塊名僅依賴其文件名和目錄路徑,極大的下降了重構的工做量)
二、爲異步加載而設計的,主要是用於瀏覽器端。AMD規範則是非同步加載模塊,容許指定回調函數
因爲Node.js主要用於服務器編程,模塊文件通常都已經存在於本地硬盤,因此加載起來比較快,不用考慮非同步加載的方式,因此CommonJS規範比較適用。可是,若是是瀏覽器環境,要從服務器端加載模塊,這時就必須採用非同步模式,所以瀏覽器端通常採用AMD規範。
二、ECMAScript 6 modules
es6 modules 的目的是建立一種能被CommonJS和AMD用戶都喜歡的語法格式:
- 與CommonJS相似,它們具備緊湊的語法、對單個導出的偏好以及對循環依賴的支持
- 與AMD相似,它們直接支持異步加載和可配置模塊加載
ES6模塊超越CommonJS和AMD的地方:
- 它們的語法比CommonJS的語法更簡潔
- 它們的結構能夠進行靜態分析(用於靜態檢查、優化等)
- 它們對循環依賴項的支持優於CommonJS
ES6模塊標準分爲兩個部分:
- import 和 export 語法 (即 named exports 和 default export,named import 和 default import,詳情可看此文章)
- 二、Programmatic loader API: to configure how modules are loaded and to conditionally load modules(程序化的加載API: 配置模塊的加載方式並有條件地加載模塊。沒懂)
三、ES6模塊語法概述
有兩種導出:named exports(每一個模塊能夠有多個 named exports)和 default export (每一個模塊只容許至多有一個)
3.1 Named exports
模塊能夠經過在聲明前加上關鍵字export來導出多個東西。這些導出以它們的名稱來區分,稱爲命名導出
![](http://static.javashuo.com/static/loading.gif)
若是須要,還能夠導入整個模塊,並經過屬性表示法引用其命名的導出
![](http://static.javashuo.com/static/loading.gif)
相同的實如今CommonJS裏以下:
![](http://static.javashuo.com/static/loading.gif)
3.2 default export
只導出單個值的模塊在node.js社區中會常常碰碰到。可是它們在前端開發中也很常見,在前端開發中,您常常爲模型提供構造函數/類,每一個模塊有一個模型。ECMAScript 6模塊能夠選擇default export,這是最重要的導出值。default export 特別容易導入。
![](http://static.javashuo.com/static/loading.gif)
default export 是 class 的ECMAScript 6模塊以下所示
![](http://static.javashuo.com/static/loading.gif)
注意: default export 導出的是一個匿名錶達式。它將經過模塊的名稱來標識,如上面兩個例子中的myFunc 和MyClass。
3.3 在一個模塊裏能夠同時存在 named exports 和 default export
下面的模式在JavaScript中很是常見的: 庫是一個函數,可是經過該函數的屬性提供了其餘服務這是很常見的,在jQuery和Underscore.js常常會碰到這種場景。
在 CommonJS 實現以下:
![](http://static.javashuo.com/static/loading.gif)
上面的例子若是是用es6 modules寫的話就是以下:
![](http://static.javashuo.com/static/loading.gif)
請注意,CommonJS 實現和ECMAScript 6 實現只是大體類似。後者具備扁平結構,而前者是嵌套的。您喜歡哪一種風格是一個品味問題,可是扁平風格具備靜態可分析的優勢(爲何這很好,將在下面解釋)。CommonJS風格的部分目的彷佛是須要對象做爲名稱空間,這種須要一般能夠經過ES6模塊的 named exports 來實現。
default export 能夠看做是一種特別的 named export
default export 實際上只是具備特殊名稱default的 named export。也就是說,下面兩個表述是等價的
![](http://static.javashuo.com/static/loading.gif)
而下面的這兩種寫法也是等價的。
![](http://static.javashuo.com/static/loading.gif)
四、設計目標
若是你想理解ECMAScript 6模塊,它有助於理解什麼目標影響了它們的設計,主要分如下幾個方面:
- 默認導出的設計
- 靜態模塊結構
- 支持同步和異步加載
- 支持模塊之間的循環依賴關係
4.1 default export
模塊語法代表default export 「是」模塊可能看起來有點奇怪,可是若是您認爲一個主要的設計目標是使默認導出儘量方便,那麼這是有意義的。
4.2 靜態模塊結構
在當前的JavaScript模塊系統中,您必須執行代碼,以查明導入和導出是什麼。這就是ECMAScript 6與這些系統不一樣的主要緣由:經過將模塊系統構建到該語言中,您能夠從語法上強制執行一個靜態模塊結構。讓咱們先看看這意味着什麼,而後看看它帶來了什麼好處。
模塊的結構是靜態的,這意味着您能夠在編譯時(靜態地)肯定導入和導出——您只須要查看源代碼,沒必要執行它。下面是CommonJS模塊如何讓這成爲不可能的兩個例子。在第一個示例中,您必須運行代碼來查找它導入的內容:
![](http://static.javashuo.com/static/loading.gif)
接下來您必須運行代碼來查找它導出的內容
ECMAScript 6給了你較少的靈活性,它強迫你保持靜態。所以,您將得到的幾個好處,下面將對此進行描述:
優勢1: 能夠更快的查找導入文件內容
若是用CommonJS的方式引入一個庫,就會返回一個對象
![](http://static.javashuo.com/static/loading.gif)
所以,經過這種方式導入庫, lib.someFunc 意味着你必須進行屬性查找,由於它是動態的,因此會更慢。
相反,若是你用es6的方式引入一個庫,您能夠靜態地瞭解其內容並優化訪問。
![](http://static.javashuo.com/static/loading.gif)
優勢2: 變量檢查
使用靜態模塊結構,您老是靜態地知道哪些變量在模塊內的任何位置可見:
- 全局變量: 惟一徹底的全局變量未來自語言自己。其餘一切都未來自模塊(包括來自標準庫和瀏覽器的功能)。也就是說,您靜態地知道全部全局變量
- 模塊導入:您也靜態地知道這些
- 模塊局部變量:能夠經過靜態檢查模塊來肯定。
這有助於檢查給定標識符是否正確。這種檢查是JSLint和JSHint等的一個流行特性;在ECMAScript 6中,大部分能夠由JavaScript引擎執行。
優勢3: 爲宏的支持作準備
宏仍然在JavaScript的將來路線圖上。若是JavaScript引擎支持宏,能夠經過庫向其添加新語法,sweet.js是一個實驗性的JavaScript宏系統,下面是來自The Sweet網站的一個例子: 一個類的宏。
![](http://static.javashuo.com/static/loading.gif)
對於宏,JavaScript引擎在編譯以前執行預處理步驟:若是解析器生成的令牌流中的令牌序列與宏的模式部分匹配,則由宏體生成的令牌替換。預處理步驟只有在可以靜態地找到宏定義時纔有效。所以,若是您想經過模塊導入宏,那麼它們必須具備靜態結構。
優勢4: 爲類型系統作準備
靜態類型檢查強加了相似於宏的約束:只有在靜態地找到類型定義時才能執行。一樣,只有具備靜態結構的模塊才能導入類型。
優勢5: 支持其餘語言
若是您但願支持將帶有宏和靜態類型的語言編譯成JavaScript,那麼JavaScript的模塊應該具備靜態結構,緣由見前兩節。
4.3 支持同步和異步加載
ECMAScript 6模塊必須獨立於引擎是同步加載模塊(例如在服務器上)仍是異步加載模塊(例如在瀏覽器中)。它的語法很是適合同步加載,異步加載是由它的靜態結構支持的:由於您能夠靜態地肯定全部導入,因此您能夠在評估模塊體以前加載它們(讓人想起AMD模塊的方式)。
4.4支持模塊之間的循環依賴
若是A(多是間接/直接的)導入B和B導入A,那麼兩個模塊A和B是循環依賴的,若是可能的話,應該避免循環依賴,它們致使A和B緊密耦合——它們只能一塊兒使用和改變。
爲何須要支持循環依賴?
循環依賴自己並非壞事。特別是對於對象,有時甚至須要這種依賴性。例如,在一些樹(如DOM文檔)中,父節點引用子節點,子節點引用父節點。在庫中,一般能夠經過仔細設計來避免循環依賴。可是在大型系統中,它們可能會發生,特別是在重構期間。若是模塊系統支持它們,那麼它將很是有用,由於在重構時系統不會崩潰。
讓咱們看看CommonJS和ECMAScript 6是如何處理循環依賴關係的。
在commonJS裏的循環依賴
在CommonJS中,若是模塊B引入當前正在計算其主體的模塊a,它將返回A當前狀態下的導出對象(下例中的第1行)。這使B可以引用其導出中該對象的屬性(第2行)。屬性在B的處理完成後填寫,此時B的導出工做正常。
![](http://static.javashuo.com/static/loading.gif)
做爲通用規則,請記住,對於循環依賴項,您不能訪問模塊主體中的導入。這是這種現象固有的,不會隨着ECMAScript 6模塊的改變而改變。
CommonJS方法的侷限性是:
一、在node.js裏,多個值的時候是不能導出一個單獨的值的,只能導出對象。你能夠像下面這樣:
module.exports = function () { ... }
若是在模塊A中這樣作,就不能在模塊B中直接使用導出函數,由於B的變量A仍然引用A的原始導出對象。
二、不能直接使用命名導出。也就是說,模塊B不能像這樣導入a.foo:
var foo = require('a').foo;
foo是沒有定義的。換句話說,您別無選擇,只能經過導出對象a引用foo。
CommonJS有一個獨特的特性:能夠在導入以前導出。保證在導入模塊的主體中能夠訪問這些導出。也就是說,若是A這麼作了,它們就能夠進入B的體內。然而,導入前導出不多有用。
在es6裏面的循環引用
爲了消除上述兩個限制,ECMAScript 6模塊導出是模塊的引用,而不是值。也就是說,到模塊主體中聲明的變量的鏈接仍然是活動的。下面的代碼演示了這一點。
![](http://static.javashuo.com/static/loading.gif)
所以,面對循環依賴關係,您是直接訪問命名導出仍是經過其模塊訪問命名導出並不重要:在這兩種狀況下都存在間接關係,並且它老是有效的。
五、關於導入和導出的一些知識
5.1 導入
es6 提供瞭如下導出方式。
![](http://static.javashuo.com/static/loading.gif)
5.2 導出
有兩種方法能夠導出當前模塊中的內容。一種能夠用關鍵字export標記聲明。
![](http://static.javashuo.com/static/loading.gif)
默認導出的「操做數」是一個表達式(包括函數表達式和類表達式)。
![](http://static.javashuo.com/static/loading.gif)
另外一種,您能夠在模塊末尾列出您想導出的全部內容(這在風格上再次與顯示模塊模式相似)。
![](http://static.javashuo.com/static/loading.gif)
你也能夠導出不一樣的名稱:
請注意,您不能使用保留字(如 default 和 new )做爲變量名,但您可使用它們做爲出口的名字(您也可使用它們做爲屬性名稱在ECMAScript 5)。若是你想直接導入這樣的命名導出,你必須爲他們重命名。
5.3 從新導出
從新導出意味着將另外一個模塊的導出添加到當前模塊的導出中。您能夠添加其餘模塊的全部導出:
![](http://static.javashuo.com/static/loading.gif)
或者你能夠更有選擇性(可選的重命名):
![](http://static.javashuo.com/static/loading.gif)
六、eval() 和 modules
eval()不支持模塊語法。它根據腳本語法規則解析它的參數,腳本不支持模塊語法(稍後解釋緣由)。若是您想計算模塊代碼,您可使用模塊加載器API(下面將介紹)。
注: (五、6兩節裏關於模塊導入導出和循環依賴的知識點能夠看一下我寫的一篇相關文章。https://juejin.im/post/5c1e58326fb9a049a570e3d5)
七、ECMAScript 6模塊加載器API
除了用於處理模塊的聲明性語法以外,還有一個編程API。它容許你:
- 以編程方式處理模塊和腳本
- 配置加載模塊
加載器處理解析模塊說明符(import…from末尾的字符串id)、加載模塊等。它們的構造函數是Reflect.Loader。每一個平臺在全局變量系統(系統加載器)中保存一個定製的實例,該實例實現其特定的模塊加載樣式
7.1 導入模塊和加載腳本
![](http://static.javashuo.com/static/loading.gif)
System.import()使您可以:
- 按需加載加載模塊
import()檢索單個模塊,可使用Promise.all()導入多個模塊
![](http://static.javashuo.com/static/loading.gif)
其餘的加載方法:
7.2 配置加載模塊
模塊加載器API具備用於配置的各類鉤子。這項工做仍在進行中。瀏覽器的第一個系統加載程序目前正在實現和測試中。其目標是找出如何最好地使模塊加載可配置。
加載器API將容許對加載過程進行許多定製。例如:
一、導入的Lint模塊(例如,經過JSLint或JSHint)。
二、在導入時自動轉換模塊(它們能夠包含CoffeeScript或TypeScript代碼)。
三、使用遺留模塊(AMD, Node.js)。
可配置模塊加載是node.js和CommonJS都有所限制的領域。
8.進一步的信息(這裏是其餘的一些延伸,感興趣的能夠去看看做者的其餘文章)
如下內容回答了兩個與ECMAScript 6模塊相關的重要問題:我今天如何使用它們?如何在HTML中嵌入它們?
- 在HTML中嵌入ES6模塊:<script>元素中的代碼不支持模塊語法,由於元素的同步特性與模塊的異步性不兼容。相反,您須要使用新的<module>元素。在
ECMAScript 6 modules in future browsers這篇博客裏,做者有解釋 <module> 的工做原理。它有幾個顯著的優點,而且能夠在其替代版本<script type="module">中進行填充。
9.ECMAScript 6模塊的好處
乍一看,將模塊構建到ECMAScript 6中彷佛是一個無聊的特性——畢竟,咱們已經有了幾個很好的模塊系統。可是ECMAScript 6模塊有一些您沒法經過庫添加的特性,好比很是緊湊的語法和靜態模塊結構(這有助於優化、靜態檢查等)。他們也將——但願——結束目前占主導地位的CommonJS和AMD之間的分裂。
對模塊有一個單一的本地標準意味着:
- 新的瀏覽器api成爲模塊,而不是導航器的全局變量或屬性
- 再也不將對象做爲名稱空間:在ECMAScript 5中,Math和JSON等對象用做函數的名稱空間。未來,這些功能能夠經過模塊提供。
出自:http://2ality.com/2014/09/es6-modules-final.html#eval-and-modules(2ality – JavaScript and more)