io.js的官網上有專門介紹其所支持的ES6特性的頁面(點我查看),上面介紹到,相比nodeJS,io.js已從根本上支持了新版V8引擎上所支持的ES6特性,無需再添加任何運行時標志(如 --harmony )。
有的朋友可能對Node不熟悉,不太知道harmony標志的情況,這里簡單介紹下。
在NodeJS中,如果所要執行的腳本(假設為app.js)是基於ES6來編寫的,那么得在命令的腳本路徑前加上 “--harmony” 運行時標志,也就是這樣執行腳本:
node --harmony app.js
“--harmony” 前綴表示讓Node支持除了 “typeof” 外的所有標准ES6特性,除此之外還有 “--harmony_typeof”(開啟typeof支持)之類的前綴,具體可以參詳stackOverflow上的一個問答。

在io.js中,所有的ES6特性被划分為三大類—— 已標准化的(completed)特性、已確定將標准化的(staged)特性、仍處草案待定狀態(in progress)的特性。
針對這三大類,io.js做了分別的處理,而不像Node那樣都得加harmony標簽:
⑴ 已標准化的ES6特性,如我們上文提到的,直接用指令執行即可,無需再加任何運行時標志;
⑵ 已確定將標准化的ES6特性,執行時需要加上運行時標志 “ --es_staging ” ,當然你也可以使用它的同義詞 “ --harmony ” ;
⑶ 在草案上但仍未確定將標准化的ES6特性,執行時需要加上它們自己對應的harmony標志,比如你在腳本中使用了 arrow_functions 特性,那么需要加上標志 “--harmony_arrow_functions”。
io.js建議不要使用 ⑵ 和 ⑶ 的不穩定的特性。
下面將較詳細地來介紹io.js原生支持的標准ES6特性。

標准ES6特性
該系列特性可直接使用,無需添加運行時標志,但小部分特性要求只能在嚴格模式("use strict";)下使用。
1. 塊級作用域/Block scoping(需要在嚴格模式下使用)
○ let
○ const
2. 集合/Collections
○ Map
○ WeakMap
○ Set
○ WeakSet
3. Generators
4. 二進制和八進制語法/Binary and Octal literals
5. Promises
6. 新的String方法/New String methods
7. 符號/Symbols
8. 字符串模板/Template strings

let (只能在嚴格模式下使用)
類似於var,聲明一個變量,但只在聲明處所處的塊級作用域內有效:
"use strict"; //使用嚴格模式 { var a=1; let b=2; console.log(a); console.log(b); } console.log(a); console.log(b); //undefined
上述代碼執行如下:

使用 let 可以用於解決變量提升問題:
"use strict"; //使用嚴格模式 for (var i = 0, a=[]; i < 10; i++) { var c = i; a[i] = function () { console.log(c); }; } a[0](); // 9 for (var i = 0, b=[]; i < 10; i++) { let c = i; //使用let解決變量提升問題 b[i] = function () { console.log(c); }; } b[0](); // 0

const (只能在嚴格模式下使用)
類似於var,聲明一個“常量” —— 初始賦值后將無法修改其值的變量。
也類似於let,只能在其所聲明的塊級作用域內來訪問到:
"use strict"; //使用嚴格模式 { const i = 1; } console.log(typeof i); //undefined { const a = 123; a = 567; //報錯,a是常量不能修改 }

塊級作用域中的函數 (只能在嚴格模式下使用)
嚴格模式下,函數本身的作用域,在其所處的塊級作用域內,可以以此解決函數聲明提升問題:
"use strict"; //使用嚴格模式 function f() { console.log('outside!'); } (function () { if(false) { // 重復聲明一次函數f function f() { console.log('inside!'); } } f(); //嚴格模式(ES6)下輸出outside;非嚴格模式(ES5)輸出inside }());

Map
新的js集合類型,類似與對象,屬於鍵值對的集合,但其“鍵”可以為任何類型,而不僅僅局限於字符串:
var map = new Map(), //新建一個map對象 o = {"a": 1, "b": 2}, s = "sth"; //map.set(key,val) 表示為該map對象添加一個新的鍵值對 map.set(o,"ooo"); map.set("a",123); map.set(null,"it`s null"); map.set(s,o); //map.delete(key) 表示刪除該map對象的某個鍵值對 map.delete(s); //map.has(key) 表示檢查該map對象是否存在某鍵名,返回對應的boolean值 console.log(map.has(o)); //true console.log(map.has(s)); //false //map.has(key) 表示獲取該map對象中某鍵名所對應的值 console.log(map.get(o)); //"ooo" console.log(map.get("a")); //123 console.log(map.get(null)); //"it`s null" //map.clear() 表示清空map對象中的全部鍵值對 map.clear(); console.log(map.get(o)); //undefined
Map支持鏈式寫法,故上方的某段代碼我們可以這么寫:
map.set(o,"ooo") .set("a",123) .set(null,"it`s null") .set(s,o) .delete(s);
我們可以用 map.size 屬性(而不是length)獲取Map中鍵值對的個數:
var map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'] ]); console.log(map.size); //3
Map提供了三種遍歷器:
map.keys() //返回鍵名的遍歷器
map.values() //返回鍵值的遍歷器
map.entries() //返回所有成員的遍歷器
我們可以使用 for...of... 方法來遍歷map的遍歷器:
"use strict"; let map = new Map([ ['F', 'no'], ['T', 'yes'] ]); for (let key of map.keys()) { console.log(key); } // "F" // "T" for (let value of map.values()) { console.log(value); } // "no" // "yes" for (let item of map.entries()) { console.log(item[0], item[1]); } // "F" "no" // "T" "yes" // 或者 let arr = [[],[]]; for (arr of map.entries()) { console.log(arr[0], arr[1]); } // 等同於使用map.entries() for (arr of map) { console.log(arr[0], arr[1]); }
其中map的entries方法等同為Map結構的默認遍歷器接口(Symbol.iterator):
console.log(map[Symbol.iterator] === map.entries); // true
另外,我們也可以使用 forEach 方法來遍歷Map對象:
var map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'] ]); map.forEach(function(value, key){ console.log("Key: %s, Value: %s", key, value); }); // Key: 1, Value: one // Key: 2, Value: two // Key: 3, Value: three

WeakMap
WeakMap結構與Map結構基本類似,區別是它只接受對象作為鍵名(null除外),不接受基礎類型的值作為鍵名,而且鍵名所指向的對象,不計入垃圾回收機制。
另外,WeakMap只有 get()、set()、has()、delete() 這么四個方法,而沒有 clear() 、size屬性和遍歷器。
WeakMap示例如下:
var wm = new WeakMap(), o = {}; // o = document.getElementById("idname"); wm.set(o,"DOM"); console.log(wm.get(o)); //"DOM"
WeakMap的設計目的在於,鍵名是對象的弱引用(垃圾回收機制不將該引用考慮在內),所以其所對應的對象可能會被自動回收。當對象被回收后,WeakMap自動移除對應的鍵值對。典型應用是,一個對應DOM元素的WeakMap結構,當某個DOM元素被清除,其所對應的WeakMap記錄就會自動被移除。基本上,WeakMap的專用場合就是,它的鍵所對應的對象,可能會在將來消失。WeakMap結構有助於防止內存泄漏。

Set
類似於數組,但是成員的值都是唯一的,沒有重復的值。另外Set也支持鏈式寫法:
var items = new Set([1,2,null,3,5,5,5,5]); //重復的值將無效處理 console.log(items.size); //5 //set.add(value) 表示添加一個set成員 items.add(2).add("aaa"); //“2”已存在,將無效處理 console.log(items.size); //6 //set.delete(value) 表示刪除一個set成員 items.delete(2); //set.has(value) 表示檢查是否存在某成員,返回相應boolean值 console.log(items.has(2)); //false items.clear(); //表示刪除全部成員 console.log(items.size); //0
跟Map一樣,Set也有keys()、values()、entries() 三種遍歷器,但遍歷到的key名等同與value值。
不過Set的默認遍歷器接口是 values() :
console.log(Set[Symbol.iterator] === Set.values); //ture
同樣的,我們也可以通過 for...of... 和 forEach 來遍歷Map:
var items = new Set([1,2,null,3,5,5,5,5]); var arr = [[],[]]; for (arr of items.entries()) { console.log(arr[0], arr[1]); } items.forEach(function(value, key){ console.log("Key: %s, Value: %s", key, value); });
Set沒有 .get(val) 方法,也不像數組那樣可以直接用索引下標取值,常規只是把Set作為數據庫索引使用(如為Redis提供索引和查詢)。

WeakSet
了解了Map和WeakMap的關系之后,相信你也很容易理解WeakSet跟Set的關系。WeakSet類同於Set,但其成員只能是對象,而且WeakSet無法遍歷也沒有size屬性。
相比Set,WeakSet能使用的方法只有 add(val) 、delete(val)、has(val) :
var ws = new WeakSet(), obj = {}, foo = {}; ws.add(foo); ws.add(obj); ws.delete(obj); console.log(ws.has(foo)); // true console.log(ws.has(obj)); // false
同WeakMap一樣,WeakSet中的對象都是弱引用,即垃圾回收機制不考慮WeakSet對該對象的引用,對象刪除時,WeakSet對應引用該對象的成員也跟着刪除。

Generators
Generator在ES6中是一個備受矚目的一個函數類型,它通過 function * funName 的形式來聲明一個Generator函數,並以 yield 語句來生成函數內部的遍歷器成員。
調用Generator函數時,函數並不執行,而是返回一個遍歷器(可以理解成暫停執行)。以后,每次調用這個遍歷器的next方法,就從函數體的頭部或者上一次停下來的地方開始執行(可以理解成恢復執行),直到遇到下一個yield語句為止:
function * gFun() { yield "第一個遍歷器成員,第一次調用next()會停在這里"; //生成遍歷器成員 var i = 0; while (true){ console.log("第"+i+"次循環"); yield i++; //生成遍歷器成員 console.log("繼續第"+i+"次循環"); } } var g = gFun(); //沒有.next()語句的調用,Generator函數不會執行,故不會造成while無限循環 console.log(g.next().value); //"第一個遍歷器成員,第一次調用next()會停在這里" console.log(g.next().value); //第0次循環 //0 console.log(g.next().value); //繼續第1次循環 //第1次循環 //1
.next().value返回了遍歷器成員的值,常規為 “yield” 或 "return" 語句所生成/返回的值。
.next() 方法除了 value 屬性外,還有判斷遍歷是否結束的 done 屬性,它返回一個表示當前遍歷是否結束的boolean值:
function * gFun() { yield "第一個遍歷器成員"; var i = 0; yield ++i; } var g = gFun(); //沒有.next()語句的調用,Generator函數不會執行,故不會造成while無限循環 console.log(g.next()); //{ value: '第一個遍歷器成員', done: false } console.log(g.next()); //{ value: 1, done: false } console.log(g.next()); //{ value: undefined, done: true }
Generator函數里比較有意思的地方是,可以給 .next() 加一個參數,該參數數值就會覆蓋掉上一個yield語句的返回值,我們可以利用該特性來進行一些有趣的運算:
function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var g = foo(5); console.log(g.next()); //{ value:6, done:false } console.log(g.next(12)); // (y /3)=(2*12 /3)=8 //{ value:8, done:false } console.log(g.next(13)); // (x + y + z)=(5 + 2*12 + 13)=42 //{ value:42, done:true }

二進制和八進制語法
原先的JS對二進制/八進制的轉換處理不太友好,我們得動用parseInt()或toString()方法,並使用對應的進制位參數:
var num = 11; console.log(parseInt(num,2)); //把num作為二進制轉為十進制 //3 console.log(parseInt(num,8)); //把num作為八進制轉為十進制 //9 console.log(num.toString(2)); //把num作為十進制轉為二進制 //1011 console.log(num.toString(8)); //把num作為十進制轉為八進制 //13
在ES6中有些許改善,比如以“0b”開頭表示二進制,以“0”開頭表示八進制,以“0x”開頭表示十六進制:
console.log(0b1101); //13 console.log(0B11); //3 console.log(071); //57 console.log(081); //81 (因為“8”超過了八進制可用數值,這里被當作十進制來轉換) console.log(0x11); //17 console.log(0X2A); //42

Promise
如果你熟悉jQuery的deferred對象,那么你會很輕松地理解ES6的Promise特性 —— 用於延遲、異步狀態處理。
一般Promise會有三種狀態:
待定(pending):初始狀態,執行、等待中,沒有被履行或拒絕。
完成(fulfilled/resolved):操作成功
拒絕(rejected):操作失敗。
Promise是一個構造函數,用來生成Promise實例。它接受一個函數作為參數,該函數又有兩個參數——resolve方法和reject方法。如果異步操作成功,則用resolve方法將Promise實例的狀態變為“成功”;如果異步操作失敗,則用reject方法將狀態變為“失敗”。
promise實例生成以后,可以用then方法分別指定resolve方法和reject方法的回調函數:
var promise = new Promise(function (resolve, reject) { if (/* 異步操作成功 */) { resolve(value); //返回resolved狀態並觸發resolve回調 } else { reject(error); //返回rejected狀態並觸發reject回調 } }); promise.then(function (value) { // resolve,即成功的回調 }, function (error) { // reject,即失敗的回調 });
這種機制對Node/io.js來說是非常有用的,因為我們知道,Node/io.js走的無阻塞異步I/O,js部分在V8執行,I/O部分在線程池做異步處理,如果我們希望在I/O操作成功或失敗后執行相應的回調函數,以常規的做法不得不在事件的回調中繼續嵌套I/O狀態的回調。但Promise的出現,很好地解決了該問題。
拿Ajax舉個例子:
var getJSON = function(url) { var promise = new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.responseType = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); function handler() { if (this.status === 200) { resolve(this.response); //成功則觸發resolve回調 } else { reject(new Error(this.statusText)); //失敗則觸發reject回調 } } }); return promise; }; getJSON("/posts.json").then(function(json) { //resolve的回調 console.log('Contents: ' + json); }, function(error) { //reject的回調 console.error('出錯了', error); });
.then() 方法返回的是一個新的Promise對象,它支持鏈式寫法,可以讓代碼邏輯更清晰。如上述 getJSON() 執行的代碼段可寫為:
getJSON("/posts.json").then(function (json) {
console.log('Contents: ' + json);
}, function (error) {
console.error('出錯了', error);
}).then(function () { //在上個then執行完之后才會執行
console.log("已經執行完並輸出信息了")
});
Promise對象還有 .all() 和 .trace() 方法,Promise.all 方法接受一個數組作為參數,數組對象為不同的Promise對象:
//接之前的代碼段 var promises = [getJSON("/post/a.json"), getJSON("/post/b.json"),getJSON("/post/c.json")]; Promise.all(promises).then(function (json) { console.log('Contents: ' + json); }, function (error) { console.error('出錯了', error); });
當數組對象的狀態都變為resolved時,Promise.all(promises)的狀態也跟着變為resolved。如果其中一個數組對象的狀態為rejected,那Promise.all(promises)則變為rejected狀態。
Promise.trace 方法跟 Promise.all 方法類似,不過只要Promise.trace的參數中的某一個對象率先改變了狀態,那么 Promise.trace(promises) 的狀態也會變為該狀態。
如上文提到的,作為trace 方法跟 all 方法的參數,要求其必須為Promise實例組成的數組,如果有非Promise實例的對象想加入到數組參數中,我們可以先通過 Promise.resolve 方法將其轉為Promise實例:
var ES6Promise = Promise.resolve($.ajax('/whatever.json')); //將一個jQuery deferred對象轉化為ES6的Promise實例
另外,我們可以使用 .catch() 來捕捉Promise對象的錯誤信息,要知道的是,Promise對象的錯誤具有“冒泡”性質,會一直向后傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲:
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 處理前兩個回調函數的錯誤
});

限於篇幅,ES6特性將分為兩篇文章來介紹,不便之處請諒解。
另本文大部分內容參考自阮一峰老師的ES6入門,但本文實例已事先對所有代碼進行了校正(包括在io.js上的兼容性、代碼錯誤的堪正等)。
共勉~

