ECMAScript 6(以下簡稱ES6)是JavaScript語言的下一代標準,已經在2015年6月正式發布了。Mozilla公司將在這個標準的基礎上,推出JavaScript 2.0。
ES6的目標,是使得JavaScript語言可以用來編寫大型的復雜的應用程序,成為企業級開發語言。
標準的制定者有計劃,以后每年發布一次標準,使用年份作為標準的版本。因為當前版本的ES6是在2015年發布的,所以又稱ECMAScript 2015。
## ECMAScript和JavaScript的關系
很多初學者感到困惑:ECMAScript和JavaScript到底是什么關系?
簡單說,ECMAScript是JavaScript語言的國際標準,JavaScript是ECMAScript的實現。
要講清楚這個問題,需要回顧歷史。1996年11月,JavaScript的創造者Netscape公司,決定將JavaScript提交給國際標準化組織ECMA,希望這種語言能夠成為國際標準。次年,ECMA發布262號標準文件(ECMA-262)的第一版,規定了瀏覽器腳本語言的標準,并將這種語言稱為ECMAScript。這個版本就是ECMAScript 1.0版。
之所以不叫JavaScript,有兩個原因。一是商標,Java是Sun公司的商標,根據授權協議,只有Netscape公司可以合法地使用JavaScript這個名字,且JavaScript本身也已經被Netscape公司注冊為商標。二是想體現這門語言的制定者是ECMA,不是Netscape,這樣有利于保證這門語言的開放性和中立性。因此,ECMAScript和JavaScript的關系是,前者是后者的規格,后者是前者的一種實現。在日常場合,這兩個詞是可以互換的。
## ECMAScript的歷史
1998年6月,ECMAScript 2.0版發布。
1999年12月,ECMAScript 3.0版發布,成為JavaScript的通行標準,得到了廣泛支持。
2007年10月,ECMAScript 4.0版草案發布,對3.0版做了大幅升級,預計次年8月發布正式版本。草案發布后,由于4.0版的目標過于激進,各方對于是否通過這個標準,發生了嚴重分歧。以Yahoo、Microsoft、Google為首的大公司,反對JavaScript的大幅升級,主張小幅改動;以JavaScript創造者Brendan Eich為首的Mozilla公司,則堅持當前的草案。
2008年7月,由于對于下一個版本應該包括哪些功能,各方分歧太大,爭論過于激進,ECMA開會決定,中止ECMAScript 4.0的開發,將其中涉及現有功能改善的一小部分,發布為ECMAScript 3.1,而將其他激進的設想擴大范圍,放入以后的版本,由于會議的氣氛,該版本的項目代號起名為Harmony(和諧)。會后不久,ECMAScript 3.1就改名為ECMAScript 5。
2009年12月,ECMAScript 5.0版正式發布。Harmony項目則一分為二,一些較為可行的設想定名為JavaScript.next繼續開發,后來演變成ECMAScript 6;一些不是很成熟的設想,則被視為JavaScript.next.next,在更遠的將來再考慮推出。
2011年6月,ECMAscript 5.1版發布,并且成為ISO國際標準(ISO/IEC 16262:2011)。
2013年3月,ECMAScript 6草案凍結,不再添加新功能。新的功能設想將被放到ECMAScript 7。
2013年12月,ECMAScript 6草案發布。然后是12個月的討論期,聽取各方反饋。
2015年6月,ECMAScript 6正式通過,成為國際標準。
ECMA的第39號技術專家委員會(Technical Committee 39,簡稱TC39)負責制訂ECMAScript標準,成員包括Microsoft、Mozilla、Google等大公司。TC39的總體考慮是,ES5與ES3基本保持兼容,較大的語法修正和新功能加入,將由JavaScript.next完成。當時,JavaScript.next指的是ES6,第六版發布以后,就指ES7。TC39的判斷是,ES5會在2013年的年中成為JavaScript開發的主流標準,并在此后五年中一直保持這個位置。
## 部署進度
各大瀏覽器的最新版本,對ES6的支持可以查看[kangax.github.io/es5-compat-table/es6/](http://kangax.github.io/es5-compat-table/es6/)。隨著時間的推移,支持度已經越來越高了,ES6的大部分特性都實現了。
Node.js和io.js(一個部署新功能更快的Node分支)是JavaScript語言的服務器運行環境。它們對ES6的支持度,比瀏覽器更高。通過它們,可以體驗更多ES6的特性。
建議使用版本管理工具[nvm](https://github.com/creationix/nvm),來安裝Node.js和io.js。不過,nvm不支持Windows系統,下面的操作可以改用[nvmw](https://github.com/hakobera/nvmw)或[nvm-windows](https://github.com/coreybutler/nvm-windows)代替。
安裝nvm需要打開命令行窗口,運行下面的命令。
~~~
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/<version number>/install.sh | bash
~~~
上面命令的version number處,需要用版本號替換。本書寫作時的版本號是v0.25.4。
該命令運行后,nvm會默認安裝在用戶主目錄的`.nvm`子目錄。然后,激活nvm。
~~~
$ source ~/.nvm/nvm.sh
~~~
激活以后,安裝Node或io.js的最新版。
~~~
$ nvm install node
# 或
$ nvm install iojs
~~~
安裝完成后,就可以在各種版本的node之間自由切換。
~~~
# 切換到node
$ nvm use node
# 切換到iojs
$ nvm use iojs
~~~
需要注意的是,Node.js對ES6的支持,需要打開harmony參數,iojs不需要。
~~~
$ node --harmony
# iojs不需要打開harmony參數
$ node
~~~
上面命令執行后,就會進入REPL環境,該環境支持所有已經實現的ES6特性。
使用下面的命令,可以查看Node.js所有已經實現的ES6特性。
~~~
$ node --v8-options | grep harmony
--harmony_typeof
--harmony_scoping
--harmony_modules
--harmony_symbols
--harmony_proxies
--harmony_collections
--harmony_observation
--harmony_generators
--harmony_iteration
--harmony_numeric_literals
--harmony_strings
--harmony_arrays
--harmony_maths
--harmony
~~~
上面命令的輸出結果,會因為版本的不同而有所不同。
我寫了一個[ES-Checker](https://github.com/ruanyf/es-checker)模塊,用來檢查各種運行環境對ES6的支持情況。訪問[ruanyf.github.io/es-checker](http://ruanyf.github.io/es-checker),可以看到您的瀏覽器支持ES6的程度。運行下面的命令,可以查看本機支持ES6的程度。
~~~
$ npm install -g es-checker
$ es-checker
~~~
## Babel轉碼器
[Babel](https://babeljs.io/)是一個廣泛使用的ES6轉碼器,可以ES6代碼轉為ES5代碼,從而在瀏覽器或其他環境執行。這意味著,你可以用ES6的方式編寫程序,又不用擔心現有環境是否支持。下面是一個例子。
~~~
// 轉碼前
input.map(item => item + 1);
// 轉碼后
input.map(function (item) {
return item + 1;
});
~~~
上面的原始代碼用了箭頭函數,這個特性還沒有得到廣泛支持,Babel將其轉為普通函數,就能在現有的JavaScript環境執行了。
它的安裝命令如下。
~~~
$ npm install --global babel
~~~
Babel自帶一個`babel-node`命令,提供支持ES6的REPL環境。它支持Node的REPL環境的所有功能,而且可以直接運行ES6代碼。
~~~
$ babel-node
>
> console.log([1,2,3].map(x => x * x))
[ 1, 4, 9 ]
>
~~~
`babel-node`命令也可以直接運行ES6腳本。假定將上面的代碼放入腳本文件`es6.js`。
~~~
$ babel-node es6.js
[1, 4, 9]
~~~
babel命令可以將ES6代碼轉為ES5代碼。
~~~
$ babel es6.js
"use strict";
console.log([1, 2, 3].map(function (x) {
return x * x;
}));
~~~
`-o`參數將轉換后的代碼,從標準輸出導入文件。
~~~
$ babel es6.js -o es5.js
# 或者
$ babel es6.js --out-file es5.js
~~~
`-d`參數用于轉換整個目錄。
~~~
$ babel -d build-dir source-dir
~~~
注意,`-d`參數后面跟的是輸出目錄。
如果希望生成source map文件,則要加上`-s`參數。
~~~
$ babel -d build-dir source-dir -s
~~~
Babel也可以用于瀏覽器。
~~~
<script src="node_modules/babel-core/browser.js"></script>
<script type="text/babel">
// Your ES6 code
</script>
~~~
上面代碼中,`browser.js`是Babel提供的轉換器腳本,可以在瀏覽器運行。用戶的ES6腳本放在script標簽之中,但是要注明`type="text/babel"`。
Babel配合Browserify一起使用,可以生成瀏覽器能夠直接加載的腳本。
~~~
$ browserify script.js -t babelify --outfile bundle.js
~~~
## Traceur轉碼器
Google公司的[Traceur](https://github.com/google/traceur-compiler)轉碼器,也可以將ES6代碼轉為ES5代碼。
### 直接插入網頁
Traceur允許將ES6代碼直接插入網頁。首先,必須在網頁頭部加載Traceur庫文件。
~~~
<!-- 加載Traceur編譯器 -->
<script src="http://google.github.io/traceur-compiler/bin/traceur.js" type="text/javascript"></script>
<!-- 將Traceur編譯器用于網頁 -->
<script src="http://google.github.io/traceur-compiler/src/bootstrap.js" type="text/javascript"></script>
<!-- 打開實驗選項,否則有些特性可能編譯不成功 -->
<script>
traceur.options.experimental = true;
</script>
~~~
接下來,就可以把ES6代碼放入上面這些代碼的下方。
~~~
<script type="module">
class Calc {
constructor(){
console.log('Calc constructor');
}
add(a, b){
return a + b;
}
}
var c = new Calc();
console.log(c.add(4,5));
</script>
~~~
正常情況下,上面代碼會在控制臺打印出9。
注意,`script`標簽的`type`屬性的值是`module`,而不是`text/javascript`。這是Traceur編譯器識別ES6代碼的標識,編譯器會自動將所有`type=module`的代碼編譯為ES5,然后再交給瀏覽器執行。
如果ES6代碼是一個外部文件,也可以用`script`標簽插入網頁。
~~~
<script type="module" src="calc.js" >
</script>
~~~
### 在線轉換
Traceur提供一個[在線編譯器](http://google.github.io/traceur-compiler/demo/repl.html),可以在線將ES6代碼轉為ES5代碼。轉換后的代碼,可以直接作為ES5代碼插入網頁運行。
上面的例子轉為ES5代碼運行,就是下面這個樣子。
~~~
<script src="http://google.github.io/traceur-compiler/bin/traceur.js"
type="text/javascript"></script>
<script src="http://google.github.io/traceur-compiler/src/bootstrap.js"
type="text/javascript"></script>
<script>
traceur.options.experimental = true;
</script>
<script>
$traceurRuntime.ModuleStore.getAnonymousModule(function() {
"use strict";
var Calc = function Calc() {
console.log('Calc constructor');
};
($traceurRuntime.createClass)(Calc, {add: function(a, b) {
return a + b;
}}, {});
var c = new Calc();
console.log(c.add(4, 5));
return {};
});
</script>
~~~
### 命令行轉換
作為命令行工具使用時,Traceur是一個Node.js的模塊,首先需要用npm安裝。
~~~
$ npm install -g traceur
~~~
安裝成功后,就可以在命令行下使用traceur了。
traceur直接運行es6腳本文件,會在標準輸出顯示運行結果,以前面的calc.js為例。
~~~
$ traceur calc.js
Calc constructor
9
~~~
如果要將ES6腳本轉為ES5保存,要采用下面的寫法
~~~
$ traceur --script calc.es6.js --out calc.es5.js
~~~
上面代碼的`--script`選項表示指定輸入文件,`--out`選項表示指定輸出文件。
為了防止有些特性編譯不成功,最好加上`--experimental`選項。
~~~
$ traceur --script calc.es6.js --out calc.es5.js --experimental
~~~
命令行下轉換的文件,就可以放到瀏覽器中運行。
### Node.js環境的用法
Traceur的Node.js用法如下(假定已安裝traceur模塊)。
~~~
var traceur = require('traceur');
var fs = require('fs');
// 將ES6腳本轉為字符串
var contents = fs.readFileSync('es6-file.js').toString();
var result = traceur.compile(contents, {
filename: 'es6-file.js',
sourceMap: true,
// 其他設置
modules: 'commonjs'
});
if (result.error)
throw result.error;
// result對象的js屬性就是轉換后的ES5代碼
fs.writeFileSync('out.js', result.js);
// sourceMap屬性對應map文件
fs.writeFileSync('out.js.map', result.sourceMap);
~~~
## ECMAScript 7
2013年3月,ES6的草案封閉,不再接受新功能了。新的功能將被加入ES7。
ES7可能包括的功能有:
(1)**Object.observe**:用來監聽對象(以及數組)的變化。一旦監聽對象發生變化,就會觸發回調函數。
(2)**Async函數**:在Promise和Generator函數基礎上,提出的異步操作解決方案。
(3)**Multi-Threading**:多線程支持。目前,Intel和Mozilla有一個共同的研究項目RiverTrail,致力于讓JavaScript多線程運行。預計這個項目的研究成果會被納入ECMAScript標準。
(4)**Traits**:它將是“類”功能(class)的一個替代。通過它,不同的對象可以分享同樣的特性。
其他可能包括的功能還有:更精確的數值計算、改善的內存回收、增強的跨站點安全、類型化的更貼近硬件的低級別操作、國際化支持(Internationalization Support)、更多的數據結構等等。
本書對于那些明確的、或者很有希望列入ES7的功能,尤其是那些Babel已經支持的功能,都將予以介紹。