io.js入門(二)—— 所支持的ES6(上)


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
View Code

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上的兼容性、代碼錯誤的堪正等)

共勉~

donate


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM