回想2007年,那時候我剛加入Mozilla's JavaScript團隊,那時候的一個典型的JavaScript程序只須要一行代碼,聽起來像個笑話。javascript
兩年後,Google Maps發佈。在這以前,JavaScript主要用來作表單的驗證,你用來處理<input onchange=>
這個程序固然只須要一行。html
時過境遷,JavaScript項目已經發展到讓人歎爲觀止,社區涌現了許多幫助開發的工具。可是最迫切須要的是一個模塊系統,它能將你的工做分散到不一樣的文件與目錄中,在須要的時候他們能彼此之間相互訪問,而且能夠有效的加載全部代碼。因此JavaScript有模塊系統這很正常,並且還有多個模塊系統(CommonJS、AMD、CMD、UMD)。不只如此,它還有幾個包管理器(npm、bower),用來安裝軟件還能拷貝一些深度依賴。你可能認爲ES6和它的新模塊系統出現得有點晚。java
那咱們來看看ES6爲現存的模塊系統添加了什麼,以及將來的標準和工具可否創建在這個系統上。首先,讓咱們看看ES6的模塊是什麼樣子的。webpack
ES6模塊是一個包含了JS代碼的文件。沒有所謂的module
關鍵詞,一個模塊看起來和一個腳本文件沒什麼不同,除了一下兩個區別:git
"use strict";
;import
和exprot
。先來談談export。在默認狀況下,模塊中全部的聲明都是私有的,若是你但願模塊中的某些聲明是公開的,並在其餘模塊中使用它們,你就必須導出它們。這裏有一些實現方法,最簡單的是添加export
關鍵字es6
// kittydar.js - Find the locations of all the cats in an image.
// (Heather Arthur wrote this library for real)
// (but she didn't use modules, because it was 2013)
export function detectCats(canvas, options) {
var kittydar = new Kittydar(options);
return kittydar.detectCats(canvas);
}
export class Kittydar {
... several methods doing image processing ...
}
// This helper function isn't exported.
function resizeCanvas() {
...
}
...複製代碼
你能夠export
任何的頂級變量:function
、class
、var
、let
、const
。github
你若是要寫一個模塊知道這麼多就夠了!你沒必要再把全部的東西放到一個當即執行函數或者回調函數裏面,只須要在你須要的地方進行聲明。因爲這個代碼是一個模塊,而不是一個腳本,全部的聲明的做用域都只屬於這個模塊,而不是全部腳本和模塊都能全局訪問的。你只要把模塊中的聲明導出成一組公共模塊的API就足夠了。web
除了導出,模塊裏的代碼和其餘普通代碼沒有什麼區別。它能夠訪問全局變量,像Object
和Array
。若是你的模塊在瀏覽器運行,還可以使用document
和XMLHttpRequest
。npm
在另外一個文件中,咱們能夠導入並使用detectCats()
函數:編程
// demo.js - Kittydar demo program
import {detectCats} from "kittydar.js";
function go() {
var canvas = document.getElementById("catpix");
var cats = detectCats(canvas);
drawRectangles(canvas, cats);
}複製代碼
要從一個模塊導入多個變量,你能夠這樣寫:
import {detectCats, Kittydar} from "kittydar.js";複製代碼
當你運行一個包含import
聲明的模塊,會先導入要導入的模塊並加載,而後根據深度優先的原則遍歷依賴圖譜來執行對應模塊,並跳過已經執行的模塊,來避免循環。
這就是模塊基礎知識,這真的很簡單。;-)
你能夠把你要導出的功能名寫在一個列表裏,而後用大括號括起來,這樣就不用在每一個要導出的功能前面加上export標記。
export {detectCats, Kittydar};
// no `export` keyword required here
function detectCats(canvas, options) { ... }
class Kittydar { ... }複製代碼
導出列表並不須要寫在文件的第一行,它能夠出如今模塊文件的頂級做用域的任何位置。你能夠有多個導出列表,或者將導出列表與導出聲明混合使用,只要不重複導出同一個變量名就行。
有時,導入的變量名碰巧與你須要使用的一些變量名衝突了,ES6容許你重命名導入的變量。
// suburbia.js
// Both these modules export something named `flip`.
// To import them both, we must rename at least one.
import {flip as flipOmelet} from "eggs.js";
import {flip as flipHouse} from "real-estate.js";
...複製代碼
一樣,你在導出變量的時候也能夠重命名它們。這在你想使用不一樣名字導出相同功能的時候十分方便。
// unlicensed_nuclear_accelerator.js - media streaming without drm
// (not a real library, but maybe it should be)
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};複製代碼
新的標準在設計上是兼容已經存在的CommonJS和AMD模塊的。若是你有一個Node項目,而且你已經執行了npm install lodash
。你使用ES6代碼可以單獨引入Lodash中的函數:
import {each, map} from "lodash";
each([3, 2, 1], x => console.log(x));複製代碼
若是你已經習慣使用_.each
而不是each
,你依然想像之前同樣使用它。或者, 你想把_
當成一個函數來使用,由於這纔是Lodash。
這種狀況下,你只要稍微改變下你的寫法:不使用花括號來導入模塊。
import _ from "lodash";複製代碼
這個寫法等同於import {default as _} from "lodash";
。全部的CommonJS 和AMD模塊在ES6中都能被看成default
導出,這個導出和你在CommonJS中使用require()
導出獲得東西同樣,即exports
對象。
ES6模塊在設計上可讓你導出更多的東西,但對於如今的CommonJS模塊,導出的default模塊就是能導出的所有東西了。例如,在寫這篇文章時,據我所知,著名的colors模塊沒有特地去支持ES6語法,這是一個CommonJS模塊組成的包,就像npm上的那些包同樣,可是你能夠直接引入到你的ES6代碼中。
// ES6 equivalent of `var colors = require("colors/safe");`
import colors from "colors/safe";複製代碼
若是你但願本身ES6模塊也具備默認導出,這很簡單。默認的導出方式並無什麼魔力;他就像其餘導出同樣,除了它的導出名爲default
。你可使用咱們以前提到的重命名語法:
let myObject = {
field1: value1,
field2: value2
};
export {myObject as default};複製代碼
或者使用簡寫:
export default {
field1: value1,
field2: value2
};複製代碼
export default
關鍵詞後面能夠跟任何值:一個函數、一個類、一個對象,全部能被命名的變量。
很差意思,這篇文章有點長。JavaScript並不孤獨:由於一些緣由,全部的語言中都有模塊系統,而且傾向於設計大量雜亂而又無聊的小特性。幸運的是咱們只剩下一個話題,噢,不對,是兩個。
import * as cows from "cows";複製代碼
當你使用import *
的時候,被引入的是一個模塊命名空間對象(module namespace object),它的屬性是模塊的輸出。若是「cows」模塊導出一個名爲moo()的函數,那麼在導入「cows」以後,你可使用cows.moo()
來進行調用。
有時候一個包的主模塊只不過是導入包其餘全部的模塊,並用統一的方式導出。爲了簡化這種代碼,有一種將導入導出所有合一的簡寫:
// world-foods.js - good stuff from all over
// import "sri-lanka" and re-export some of its exports
export {Tea, Cinnamon} from "sri-lanka";
// import "equatorial-guinea" and re-export some of its exports
export {Coffee, Cocoa} from "equatorial-guinea";
// import "singapore" and export ALL of its exports
export * from "singapore";複製代碼
這種export-from
表達式相似於import-from
後面跟了一個export
。這和真正的導入有一些區別,它不會在當前做用域中綁定將要導出的變量。若是你打算在world-foods.js
中使用Tea
來編寫一些代碼,請不要使用這種簡寫,你會發現Tea爲定義。
若是「singapore」導出的命名與其餘導出發生了衝突,那就會出現錯誤,因此請謹慎使用。
呼,咱們已經把語法介紹完了!下面來談談一些有趣的事情。
import
到底作了什麼?無論你信不信,它什麼都沒作。
噢,你看起來沒那麼好騙。那麼你會相信標準幾乎沒有說import
到底該怎麼作嗎?這是件好事嗎?(做者貌似很愛開玩笑。)
ES6將模塊的加載細節徹底交給了實現,其他的模塊執行部分卻規定得很是詳細。
簡單來講,當你告訴JS引擎運行一個模塊的時候,它的行爲能夠概括爲如下四部:
import {cake} from "paleo"
,可是「paleo」模塊沒真正導出名爲cake的變量,你會獲得一個錯誤。這很糟糕,由於你離運行js並品嚐蛋糕只有一步之遙。import
過程已經完成了,因此前面說代碼執行到import
這一行聲明時,什麼都沒有發生。看到沒?我說了什麼都不會發生,在編程語言這件事上,我歷來都不說慌。
如今咱們能夠開始介紹這個系統中有趣的部分了。這有一個很是炫酷的技巧。因爲系統沒有指定如何加載的這方面的細節,而且你能夠經過查看源代碼中導入的聲明,提早計算出全部的依賴項,因此ES6的實現能夠經過預處理器完成全部的工做,而後把全部的模塊打包到一個文件中,最後經過網絡進行請求一次便可。像webpack這樣的工具就是這麼作的。
這是一個優雅的解決方案,由於經過網絡加載全部的腳本文件很耗時,假如你請求一個資源後,發現裏面有import
聲明,而後你又得請求更多資源。一個加載器須要很是多的網絡請求來回傳輸。經過webpack,你不只能在今天就使用ES6的模塊話,你還能得到不少好處,而且不須要擔憂會形成運行時的性能降低。
本來是有計劃制定一個ES6中模塊加載的詳細規範的,而且已經初步成型。它沒有成爲標準的緣由之一是不知道如何與打包這一特性進行整合。我但願模塊化的加載會更加標準化,也但願打包工具會愈來愈好。
做爲一個動態編譯語言,使人驚奇的是JavaScript擁有一個靜態的模塊系統。
import
和export
只能寫在頂級做用域中。你不能在條件判斷語句和函數做用域內使用import
。import
懶加載相關的語法。(如今import()方法已經在提案中了)import
的錯誤,沒法進行recovery。一個應用可能依賴許多的模塊,一旦有一個模塊加載失敗,這個應用都不會運行。你不能在try/catch
中使用import
。正是由於es6的模塊表現得如此靜態,webpack才能在編譯的時候檢測出代碼中的錯誤。若是你的需求是靜態的,ES6的模塊系統仍是至關不錯的。可是你有時候你仍是向進行一些hack,對吧?
這就是爲何你使用的模塊加載系統會提供一些系統層次的API來配合ES6的靜態的import/export
語法。例如,webpack有一個API能進行代碼的分割,按照你的需求對一些模塊進行懶加載。這個API可以打破以前列出的規矩。
ES6的模塊語法很是靜態,這很好-在使用一些編譯工具時咱們都能嚐到一些甜頭。
靜態語法的設計可讓它與動態加載器豐富的API進行工做。
若是你今天就想使用,你須要一個預編譯器,如 Traceur 和 Babel 。這個系列以前也有相關文章,Gastón I. Silva:如何使用 Babel 和 Broccoli 編譯 ES6 代碼爲 web 可用。Gastón也將案例放在了 GitHub 上。另外這篇文章也介紹瞭如何使用 Babel 和 webpack。
ES6 模塊系統由 Dave Herman 和 Sam Tobin-Hochstadt進行設計,他們不顧多人(包括我)的反對,多年來始終堅持模塊系統是靜態的。JonCoppeard正在Firefox上實現ES6的模塊化功能。JavaScript Loader的相關標準的工做也正在進行中,預計在HTML中將會被添加相似<script type=module>
這樣的東西。
這即是 ES6 了。
這太有趣了,我不但願如今就結束。也許咱們還能再說一會。咱們還可以討論一些關於ES6規範中零零碎碎的東西,但這些又不足夠寫成文章。也行會有一些關於ES6將來特性的一些東西,盡請期待下週的ES6 In Depth