ECMAScript 6.0 簡介

ECMAScript 6.0(如下簡稱 ES6)是 JavaScript 語言的下一代標準,已經在2015年6月正式發佈了。它的目標,是使得 JavaScript 語言能夠用來編寫複雜的大型應用程序,成爲企業級開發語言。javascript

ECMAScript 和 JavaScript 的關係

一個常見的問題是,ECMAScript 和 JavaScript 究竟是什麼關係?html

要講清楚這個問題,須要回顧歷史。1996年11月,JavaScript 的創造者 Netscape 公司,決定將 JavaScript 提交給國際標準化組織ECMA,但願這種語言可以成爲國際標準。次年,ECMA 發佈262號標準文件(ECMA-262)的初版,規定了瀏覽器腳本語言的標準,並將這種語言稱爲 ECMAScript,這個版本就是1.0版。java

該標準從一開始就是針對 JavaScript 語言制定的,可是之因此不叫 JavaScript,有兩個緣由。一是商標,Java 是 Sun 公司的商標,根據受權協議,只有 Netscape 公司能夠合法地使用 JavaScript 這個名字,且 JavaScript 自己也已經被 Netscape 公司註冊爲商標。二是想體現這門語言的制定者是 ECMA,不是 Netscape,這樣有利於保證這門語言的開放性和中立性。node

所以,ECMAScript 和 JavaScript 的關係是,前者是後者的規格,後者是前者的一種實現(另外的 ECMAScript 方言還有 Jscript 和 ActionScript)。平常場合,這兩個詞是能夠互換的。react

ES6 與 ECMAScript 2015 的關係

ECMAScript 2015(簡稱 ES2015)這個詞,也是常常能夠看到的。它與 ES6 是什麼關係呢?git

2011年,ECMAScript 5.1版發佈後,就開始制定6.0版了。所以,ES6 這個詞的原意,就是指 JavaScript 語言的下一個版本。es6

可是,由於這個版本引入的語法功能太多,並且制定過程中,還有不少組織和我的不斷提交新功能。事情很快就變得清楚了,不可能在一個版本里麪包括全部將要引入的功能。常規的作法是先發布6.0版,過一段時間再發6.1版,而後是6.2版、6.3版等等。github

可是,標準的制定者不想這樣作。他們想讓標準的升級成爲常規流程:任何人在任什麼時候候,均可以向標準委員會提交新語法的提案,而後標準委員會每月開一次會,評估這些提案是否能夠接受,須要哪些改進。若是通過屢次會議之後,一個提案足夠成熟了,就能夠正式進入標準了。這就是說,標準的版本升級成爲了一個不斷滾動的流程,每月都會有變更。ajax

標準委員會最終決定,標準在每一年的6月份正式發佈一次,做爲當年的正式版本。接下來的時間,就在這個版本的基礎上作改動,直到下一年的6月份,草案就天然變成了新一年的版本。這樣一來,就不須要之前的版本號了,只要用年份標記就能夠了。npm

ES6 的第一個版本,就這樣在2015年6月發佈了,正式名稱就是《ECMAScript 2015標準》(簡稱 ES2015)。2016年6月,小幅修訂的《ECMAScript 2016標準》(簡稱 ES2016)如期發佈,這個版本能夠看做是 ES6.1 版,由於二者的差別很是小(只新增了數組實例的includes方法和指數運算符),基本上是同一個標準。根據計劃,2017年6月發佈 ES2017 標準。

所以,ES6 既是一個歷史名詞,也是一個泛指,含義是5.1版之後的 JavaScript 的下一代標準,涵蓋了ES201五、ES201六、ES2017等等,而ES2015 則是正式名稱,特指該年發佈的正式版本的語言標準。本書中提到 ES6 的地方,通常是指 ES2015 標準,但有時也是泛指「下一代 JavaScript 語言」。

語法提案的批准流程

任何人均可以向標準委員會(又稱 TC39 委員會)提案,要求修改語言標準。

一種新的語法從提案到變成正式標準,須要經歷五個階段。每一個階段的變更都須要由 TC39 委員會批准。

  • Stage 0 - Strawman(展現階段)
  • Stage 1 - Proposal(徵求意見階段)
  • Stage 2 - Draft(草案階段)
  • Stage 3 - Candidate(候選人階段)
  • Stage 4 - Finished(定案階段)

一個提案只要能進入 Stage 2,就差很少確定會包括在之後的正式標準裏面。ECMAScript 當前的全部提案,能夠在 TC39 的官方網站Github.com/tc39/ecma262查看。

本書的寫做目標之一,是跟蹤 ECMAScript 語言的最新進展,介紹5.1版本之後全部的新語法。對於那些明確或頗有但願,將要列入標準的新語法,都將予以介紹。

ECMAScript 的歷史

ES6 從開始制定到最後發佈,整整用了15年。

前面提到,ECMAScript 1.0 是1997年發佈的,接下來的兩年,連續發佈了 ECMAScript 2.0(1998年6月)和 ECMAScript 3.0(1999年12月)。3.0版是一個巨大的成功,在業界獲得普遍支持,成爲通行標準,奠基了 JavaScript 語言的基本語法,之後的版本徹底繼承。直到今天,初學者一開始學習 JavaScript,其實就是在學3.0版的語法。

2000年,ECMAScript 4.0 開始醞釀。這個版本最後沒有經過,可是它的大部份內容被 ES6 繼承了。所以,ES6 制定的起點實際上是2000年。

爲何 ES4 沒有經過呢?由於這個版本太激進了,對 ES3 作了完全升級,致使標準委員會的一些成員不肯意接受。ECMA 的第39號技術專家委員會(Technical Committee 39,簡稱TC39)負責制訂 ECMAScript 標準,成員包括 Microsoft、Mozilla、Google 等大公司。

2007年10月,ECMAScript 4.0 版草案發布,原本預計次年8月發佈正式版本。可是,各方對因而否經過這個標準,發生了嚴重分歧。以 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,在更遠的未來再考慮推出。TC39 委員會的整體考慮是,ES5 與 ES3 基本保持兼容,較大的語法修正和新功能加入,將由 JavaScript.next 完成。當時,JavaScript.next 指的是ES6,第六版發佈之後,就指 ES7。TC39 的判斷是,ES5 會在2013年的年中成爲 JavaScript 開發的主流標準,並在此後五年中一直保持這個位置。

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 正式經過,成爲國際標準。從2000年算起,這時已通過去了15年。

部署進度

各大瀏覽器的最新版本,對 ES6 的支持能夠查看kangax.github.io/es5-compat-table/es6/。隨着時間的推移,支持度已經愈來愈高了,超過90%的 ES6 語法特性都實現了。

Node 是 JavaScript 的服務器運行環境(runtime)。它對 ES6 的支持度更高。除了那些默認打開的功能,還有一些語法功能已經實現了,可是默認沒有打開。使用下面的命令,能夠查看 Node 已經實現的 ES6 特性。

$ node --v8-options | grep harmony

上面命令的輸出結果,會由於版本的不一樣而有所不一樣。

我寫了一個工具 ES-Checker,用來檢查各類運行環境對 ES6 的支持狀況。訪問ruanyf.github.io/es-checker,能夠看到您的瀏覽器支持 ES6 的程度。運行下面的命令,能夠查看你正在使用的 Node 環境對 ES6 的支持程度。

$ npm install -g es-checker
$ es-checker

=========================================
Passes 24 feature Dectations
Your runtime supports 57% of ECMAScript 6
=========================================

Babel 轉碼器

Babel 是一個普遍使用的 ES6 轉碼器,能夠將 ES6 代碼轉爲 ES5 代碼,從而在現有環境執行。這意味着,你能夠用 ES6 的方式編寫程序,又不用擔憂現有環境是否支持。下面是一個例子。

// 轉碼前
input.map(item => item + 1);

// 轉碼後
input.map(function (item) {
  return item + 1;
});

上面的原始代碼用了箭頭函數,Babel 將其轉爲普通函數,就能在不支持箭頭函數的 JavaScript 環境執行了。

配置文件.babelrc

Babel 的配置文件是.babelrc,存放在項目的根目錄下。使用 Babel 的第一步,就是配置這個文件。

該文件用來設置轉碼規則和插件,基本格式以下。

{
  "presets": [],
  "plugins": []
}

presets字段設定轉碼規則,官方提供如下的規則集,你能夠根據須要安裝。

# 最新轉碼規則
$ npm install --save-dev babel-preset-latest

# react 轉碼規則
$ npm install --save-dev babel-preset-react

# 不一樣階段語法提案的轉碼規則(共有4個階段),選裝一個
$ npm install --save-dev babel-preset-stage-0
$ npm install --save-dev babel-preset-stage-1
$ npm install --save-dev babel-preset-stage-2
$ npm install --save-dev babel-preset-stage-3

而後,將這些規則加入.babelrc

{
    "presets": [
      "latest",
      "react",
      "stage-2"
    ],
    "plugins": []
  }

注意,如下全部 Babel工具和模塊的使用,都必須先寫好.babelrc

命令行轉碼babel-cli

Babel提供babel-cli工具,用於命令行轉碼。

它的安裝命令以下。

$ npm install --global babel-cli

基本用法以下。

# 轉碼結果輸出到標準輸出
$ babel example.js

# 轉碼結果寫入一個文件
# --out-file 或 -o 參數指定輸出文件
$ babel example.js --out-file compiled.js
# 或者
$ babel example.js -o compiled.js

# 整個目錄轉碼
# --out-dir 或 -d 參數指定輸出目錄
$ babel src --out-dir lib
# 或者
$ babel src -d lib

# -s 參數生成source map文件
$ babel src -d lib -s

上面代碼是在全局環境下,進行 Babel 轉碼。這意味着,若是項目要運行,全局環境必須有 Babel,也就是說項目產生了對環境的依賴。另外一方面,這樣作也沒法支持不一樣項目使用不一樣版本的 Babel。

一個解決辦法是將babel-cli安裝在項目之中。

# 安裝
$ npm install --save-dev babel-cli

而後,改寫package.json

{
  // ...
  "devDependencies": {
    "babel-cli": "^6.0.0"
  },
  "scripts": {
    "build": "babel src -d lib"
  },
}

轉碼的時候,就執行下面的命令。

$ npm run build

babel-node

babel-cli工具自帶一個babel-node命令,提供一個支持ES6的REPL環境。它支持Node的REPL環境的全部功能,並且能夠直接運行ES6代碼。

它不用單獨安裝,而是隨babel-cli一塊兒安裝。而後,執行babel-node就進入REPL環境。

$ babel-node
> (x => x * 2)(1)
2

babel-node命令能夠直接運行ES6腳本。將上面的代碼放入腳本文件es6.js,而後直接運行。

$ babel-node es6.js
2

babel-node也能夠安裝在項目中。

$ npm install --save-dev babel-cli

而後,改寫package.json

{
  "scripts": {
    "script-name": "babel-node script.js"
  }
}

上面代碼中,使用babel-node替代node,這樣script.js自己就不用作任何轉碼處理。

babel-register

babel-register模塊改寫require命令,爲它加上一個鉤子。此後,每當使用require加載.js.jsx.es.es6後綴名的文件,就會先用Babel進行轉碼。

$ npm install --save-dev babel-register

使用時,必須首先加載babel-register

require("babel-register");
require("./index.js");

而後,就不須要手動對index.js轉碼了。

須要注意的是,babel-register只會對require命令加載的文件轉碼,而不會對當前文件轉碼。另外,因爲它是實時轉碼,因此只適合在開發環境使用。

babel-core

若是某些代碼須要調用 Babel 的 API 進行轉碼,就要使用babel-core模塊。

安裝命令以下。

$ npm install babel-core --save

而後,在項目中就能夠調用babel-core

var babel = require('babel-core');

// 字符串轉碼
babel.transform('code();', options);
// => { code, map, ast }

// 文件轉碼(異步)
babel.transformFile('filename.js', options, function(err, result) {
  result; // => { code, map, ast }
});

// 文件轉碼(同步)
babel.transformFileSync('filename.js', options);
// => { code, map, ast }

// Babel AST轉碼
babel.transformFromAst(ast, code, options);
// => { code, map, ast }

配置對象options,能夠參看官方文檔http://babeljs.io/docs/usage/options/

下面是一個例子。

var es6Code = 'let x = n => n + 1';
var es5Code = require('babel-core')
  .transform(es6Code, {
    presets: ['latest']
  })
  .code;
// '"use strict";\n\nvar x = function x(n) {\n  return n + 1;\n};'

上面代碼中,transform方法的第一個參數是一個字符串,表示須要被轉換的ES6代碼,第二個參數是轉換的配置對象。

babel-polyfill

Babel 默認只轉換新的 JavaScript 句法(syntax),而不轉換新的 API,好比IteratorGeneratorSetMapsProxyReflectSymbolPromise等全局對象,以及一些定義在全局對象上的方法(好比Object.assign)都不會轉碼。

舉例來講,ES6在Array對象上新增了Array.from方法。Babel 就不會轉碼這個方法。若是想讓這個方法運行,必須使用babel-polyfill,爲當前環境提供一個墊片。

安裝命令以下。

$ npm install --save babel-polyfill

而後,在腳本頭部,加入以下一行代碼。

import 'babel-polyfill';
// 或者
require('babel-polyfill');

Babel 默認不轉碼的 API 很是多,詳細清單能夠查看babel-plugin-transform-runtime模塊的definitions.js文件。

瀏覽器環境

Babel 也能夠用於瀏覽器環境。可是,從 Babel 6.0 開始,再也不直接提供瀏覽器版本,而是要用構建工具構建出來。若是你沒有或不想使用構建工具,可使用babel-standalone模塊提供的瀏覽器版本,將其插入網頁。

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.4.4/babel.min.js"></script>
<script type="text/babel">
// Your ES6 code
</script>

注意,網頁實時將 ES6 代碼轉爲 ES5,對性能會有影響。生產環境須要加載已經轉碼完成的腳本。

下面是如何將代碼打包成瀏覽器可使用的腳本,以Babel配合Browserify爲例。首先,安裝babelify模塊。

$ npm install --save-dev babelify babel-preset-latest

而後,再用命令行轉換 ES6 腳本。

$  browserify script.js -o bundle.js \
  -t [ babelify --presets [ latest ] ]

上面代碼將ES6腳本script.js,轉爲bundle.js,瀏覽器直接加載後者就能夠了。

package.json設置下面的代碼,就不用每次命令行都輸入參數了。

{
  "browserify": {
    "transform": [["babelify", { "presets": ["latest"] }]]
  }
}

在線轉換

Babel 提供一個REPL在線編譯器,能夠在線將 ES6 代碼轉爲 ES5 代碼。轉換後的代碼,能夠直接做爲 ES5 代碼插入網頁運行。

與其餘工具的配合

許多工具須要 Babel 進行前置轉碼,這裏舉兩個例子:ESLint 和 Mocha。

ESLint 用於靜態檢查代碼的語法和風格,安裝命令以下。

$ npm install --save-dev eslint babel-eslint

而後,在項目根目錄下,新建一個配置文件.eslintrc,在其中加入parser字段。

{
  "parser": "babel-eslint",
  "rules": {
    ...
  }
}

再在package.json之中,加入相應的scripts腳本。

{
    "name": "my-module",
    "scripts": {
      "lint": "eslint my-files.js"
    },
    "devDependencies": {
      "babel-eslint": "...",
      "eslint": "..."
    }
  }

Mocha 則是一個測試框架,若是須要執行使用 ES6 語法的測試腳本,能夠修改package.jsonscripts.test

"scripts": {
  "test": "mocha --ui qunit --compilers js:babel-core/register"
}

上面命令中,--compilers參數指定腳本的轉碼器,規定後綴名爲js的文件,都須要使用babel-core/register先轉碼。

Traceur 轉碼器

Google公司的Traceur轉碼器,也能夠將 ES6 代碼轉爲 ES5 代碼。

直接插入網頁

Traceur 容許將 ES6 代碼直接插入網頁。首先,必須在網頁頭部加載 Traceur 庫文件。

<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/bin/BrowserSystem.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<script type="module">
  import './Greeter.js';
</script>

上面代碼中,一共有4個script標籤。第一個是加載 Traceur 的庫文件,第二個和第三個是將這個庫文件用於瀏覽器環境,第四個則是加載用戶腳本,這個腳本里面可使用ES6代碼。

注意,第四個script標籤的type屬性的值是module,而不是text/javascript。這是 Traceur 編譯器識別 ES6 代碼的標誌,編譯器會自動將全部type=module的代碼編譯爲 ES5,而後再交給瀏覽器執行。

除了引用外部 ES6 腳本,也能夠直接在網頁中放置 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

若是想對 Traceur 的行爲有精確控制,能夠採用下面參數配置的寫法。

<script>
  // Create the System object
  window.System = new traceur.runtime.BrowserTraceurLoader();
  // Set some experimental options
  var metadata = {
    traceurOptions: {
      experimental: true,
      properTailCalls: true,
      symbols: true,
      arrayComprehension: true,
      asyncFunctions: true,
      asyncGenerators: exponentiation,
      forOn: true,
      generatorComprehension: true
    }
  };
  // Load your module
  System.import('./myModule.js', {metadata: metadata}).catch(function(ex) {
    console.error('Import failed', ex.stack || ex);
  });
</script>

上面代碼中,首先生成Traceur的全局對象window.System,而後System.import方法能夠用來加載 ES6。加載的時候,須要傳入一個配置對象metadata,該對象的traceurOptions屬性能夠配置支持 ES6 功能。若是設爲experimental: true,就表示除了 ES6 之外,還支持一些實驗性的新功能。

在線轉換

Traceur也提供一個在線編譯器,能夠在線將 ES6 代碼轉爲 ES5 代碼。轉換後的代碼,能夠直接做爲 ES5 代碼插入網頁運行。

上面的例子轉爲 ES5 代碼運行,就是下面這個樣子。

<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/bin/BrowserSystem.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></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 的模塊,首先須要用 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 環境的用法

Traceur 的 Node用法以下(假定已安裝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);
相關文章
相關標籤/搜索