ES6(簡介及常用)-下
八、Iterator 和 for of 值遍歷(谷歌瀏覽器無)
1、Iterator(遍歷器)的概念
JavaScript 原有的表示“集合”的數據結構,主要是數組(Array)和對象(Object),ES6 又添加了Map和Set。這樣就有了四種數據集合,用戶還可以組合使用它們,定義自己的數據結構,比如數組的成員是Map,Map的成員是對象。這樣就需要一種統一的接口機制,來處理所有不同的數據結構。遍歷器(Iterator)就是這樣一種機制。它是一種接口,為各種不同的數據結構提供統一的訪問機制。任何數據結構只要部署Iterator接口,就可以完成遍歷操作(即依次處理該數據結構的所有成員)。
(背下來)Iterator 的作用有三個:一是為各種數據結構,提供一個統一的、簡便的訪問接口;二是使得數據結構的成員能夠按某種次序排列;三是ES6創造了一種新的遍歷命令for...of循環,Iterator接口主要供for...of消費。Iterator 的遍歷過程是這樣的。(1)創建一個指針對象,指向當前數據結構的起始位置。也就是說,遍歷器對象本質上,就是一個指針對象。(2)第一次調用指針對象的next方法,可以將指針指向數據結構的第一個成員。(3)第二次調用指針對象的next方法,指針就指向數據結構的第二個成員。(4)不斷調用指針對象的next方法,直到它指向數據結構的結束位置。每一次調用next方法,都會返回數據結構的當前成員的信息。具體來說,就是返回一個包含value和done兩個屬性的對象。其中,value屬性是當前成員的值,done屬性是一個布爾值,表示遍歷是否結束。
2、默認 Iterator 接口
Iterator 接口的目的,就是為所有數據結構,提供了一種統一的訪問機制,即for...of循環。當使用for...of循環遍歷某種數據結構時,該循環會自動去尋找 Iterator 接口。一種數據結構只要部署了 Iterator 接口,我們就稱這種數據結構是”可遍歷的“(iterable)。
ES6 規定,默認的 Iterator 接口部署在數據結構的Symbol.iterator屬性。Symbol.iterator屬性本身是一個函數,就是當前數據結構默認的遍歷器生成函數。執行這個函數,就會返回一個遍歷器。至於屬性名Symbol.iterator,它是一個表達式,返回Symbol對象的iterator屬性,這是一個預定義好的、類型為 Symbol 的特殊值,所以要放在方括號內
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
上面代碼中,對象obj是可遍歷的(iterable),因為具有Symbol.iterator屬性。執行這個屬性,會返回一個遍歷器對象。該對象的根本特征就是具有next方法。每次調用next方法,都會返回一個代表當前成員的信息對象,具有value和done兩個屬性。
ES6 的有些數據結構原生具備 Iterator 接口(比如數組),即不用任何處理,就可以被for...of循環遍歷。原因在於,這些數據結構原生部署了Symbol.iterator屬性,另外一些數據結構沒有(比如對象)。凡是部署了Symbol.iterator屬性的數據結構,就稱為部署了遍歷器接口。調用這個接口,就會返回一個遍歷器對象。字符串是一個類似數組的對象,也原生具有 Iterator 接口
3、for...of循環
ES6 借鑒 C++、Java、C# 和 Python 語言,引入了for...of循環,作為遍歷所有數據結構的統一的方法。一個數據結構只要部署了Symbol.iterator屬性,就被視為具有iterator接口,就可以用for...of循環遍歷它的成員。也就是說,for...of循環內部調用的是數據結構的Symbol.iterator方法。for...of循環可以使用的範圍包括數組、Set 和 Map 結構、某些類似數組的對象(比如arguments對象、DOM NodeList 對象)、Generator 對象,以及字符串。
4、數組
數組原生具備iterator接口(即默認部署了Symbol.iterator屬性),for...of循環本質上就是調用這個接口產生的遍歷器,可以用下面的代碼證明。
const arr = [‘red‘, ‘green‘, ‘blue‘];
for(let v of arr) {
console.log(v); // red green blue
}
const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);
for(let v of obj) {
console.log(v); // red green blue
}
上面代碼中,空對象obj部署了數組arr的Symbol.iterator屬性,結果obj的for...of循環,產生了與arr完全一樣的結果。
for...of循環可以代替數組實例的forEach方法。
const arr = [‘red‘, ‘green‘, ‘blue‘];
arr.forEach(function (element, index) {
console.log(element); // red green blue
console.log(index); // 0 1 2
});
JavaScript 原有的for...in循環,只能獲得對象的鍵名,不能直接獲取鍵值。ES6 提供for...of循環,允許遍歷獲得鍵值。
var arr = [‘a‘, ‘b‘, ‘c‘, ‘d‘];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
for (let a of arr) {
console.log(a); // a b c d
}
上面代碼表明,for...in循環讀取鍵名,for...of循環讀取鍵值。如果要通過for...of循環,獲取數組的索引,可以借助數組實例的entries方法和keys方法,參見《數組的擴展》章節。
for...of循環調用遍歷器接口,數組的遍歷器接口只返回具有數字索引的屬性。這一點跟for...in循環也不一樣。
let arr = [3, 5, 7];
arr.foo = ‘hello‘;
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
上面代碼中,for...of循環不會返回數組arr的foo屬性。
九、模塊(選)
在ES6標準中,JavaScript原生支持module了。這種將JS代碼分割成不同功能的小塊進行模塊化的概念是在一些三方規範中流行起來的,比如CommonJS和AMD模式。
將不同功能的代碼分別寫在不同文件中,各模塊只需導出公共接口部分,然後通過模塊的導入的方式可以在其他地方使用。下面的例子來自tutsplus:
// point.js
module "point" {
export class Point {
constructor (x, y) {
public x = x;
public y = y;
}
}
}
// myapp.js
//聲明引用的模塊
module point from "/point.js";
//這裏可以看出,盡管聲明了引用的模塊,還是可以通過指定需要的部分進行導入
import Point from "point";
var origin = new Point(0, 0);
console.log(origin);
十、Map,Set 數據結構
1、ES6 提供了新的數據結構 Set。它類似於數組,但是成員的值都是唯一的,沒有重復的值。向Set加入值的時候,不會發生類型轉換,所以5和"5"是兩個不同的值。Set內部判斷兩個值是否不同,使用的算法叫做“Same-value equality”,它類似於精確相等運算符(===),主要的區別是NaN等於自身,而精確相等運算符認為NaN不等於自身。另外,兩個對象總是不相等的。例如兩個空對象不相等,所以它們被視為兩個值。
Set 結構的實例有以下屬性:Set.prototype.constructor:構造函數,默認就是Set函數;Set.prototype.size:返回Set實例的成員總數。
Set 實例的方法分為兩大類:操作方法(用於操作數據)和遍歷方法(用於遍歷成員)。下面先介紹四個操作方法。
add(value):添加某個值,返回Set結構本身。
delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
has(value):返回一個布爾值,表示該值是否為Set的成員。
clear():清除所有成員,沒有返回值。
遍歷操作
Set 結構的實例有四個遍歷方法,可以用於遍歷成員。
keys():返回鍵名的遍歷器
values():返回鍵值的遍歷器
entries():返回鍵值對的遍歷器
forEach():使用回調函數遍歷每個成員
需要特別指出的是,Set的遍歷順序就是插入順序。這個特性有時非常有用,比如使用Set保存一個回調函數列表,調用時就能保證按照添加順序調用。
let set = new Set([‘red‘, ‘green‘, ‘blue‘]);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
上面代碼中,entries方法返回的遍歷器,同時包括鍵名和鍵值,所以每次輸出一個數組,它的兩個成員完全相等。Set 結構的實例默認可遍歷,它的默認遍歷器生成函數就是它的values方法。
2、WeakSet 結構與 Set 類似,也是不重復的值的集合。但是,它與 Set 有兩個區別。首先,WeakSet 的成員只能是對象,而不能是其他類型的值。
const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
上面代碼試圖向 WeakSet 添加一個數值和Symbol值,結果報錯,因為 WeakSet 只能放置對象。
其次,WeakSet 中的對象都是弱引用,即垃圾回收機制不考慮 WeakSet 對該對象的引用,也就是說,如果其他對象都不再引用該對象,那麽垃圾回收機制會自動回收該對象所占用的內存,不考慮該對象還存在於 WeakSet 之中。
這是因為垃圾回收機制依賴引用計數,如果一個值的引用次數不為0,垃圾回收機制就不會釋放這塊內存。結束使用該值之後,有時會忘記取消引用,導致內存無法釋放,進而可能會引發內存泄漏。WeakSet 裏面的引用,都不計入垃圾回收機制,所以就不存在這個問題。因此,WeakSet 適合臨時存放一組對象,以及存放跟對象綁定的信息。只要這些對象在外部消失,它在 WeakSet 裏面的引用就會自動消失。
由於上面這個特點,WeakSet 的成員是不適合引用的,因為它會隨時消失。另外,由於 WeakSet 內部有多少個成員,取決於垃圾回收機制有沒有運行,運行前後很可能成員個數是不一樣的,而垃圾回收機制何時運行是不可預測的,因此 ES6 規定 WeakSet 不可遍歷。
WeakSet 是一個構造函數,可以使用new命令,創建 WeakSet 數據結構。
const ws = new WeakSet();
作為構造函數,WeakSet 可以接受一個數組或類似數組的對象作為參數。(實際上,任何具有 Iterable 接口的對象,都可以作為 WeakSet 的參數。)該數組的所有成員,都會自動成為 WeakSet 實例對象的成員。
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
上面代碼中,a是一個數組,它有兩個成員,也都是數組。將a作為 WeakSet構造函數的參數,a的成員會自動成為 WeakSet 的成員。
註意,是a數組的成員成為 WeakSet 的成員,而不是a數組本身。這意味著,數組的成員只能是對象。
const b = [3, 4];
const ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)
上面代碼中,數組b的成員不是對象,加入 WeaKSet 就會報錯。
WeakSet 結構有以下三個方法。
WeakSet.prototype.add(value):向 WeakSet 實例添加一個新成員。
WeakSet.prototype.delete(value):清除 WeakSet 實例的指定成員。
WeakSet.prototype.has(value):返回一個布爾值,表示某個值是否在
3、Map
ES6 提供了 Map 數據結構。它類似於對象,也是鍵值對的集合,但是“鍵”的範圍不限於字符串,各種類型的值(包括對象)都可以當作鍵。也就是說,Object 結構提供了“字符串—值”的對應,Map結構提供了“值—值”的對應。如果你需要“鍵值對”的數據結構,Map 比 Object 更合適。
const m = new Map();
const o = {p: ‘Hello World‘};
m.set(o, ‘content‘)
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
上面代碼使用 Map 結構的set方法,將對象o當作m的一個鍵,然後又使用get方法讀取這個鍵,接著使用delete方法刪除了這個鍵。
上面的例子展示了如何向 Map 添加成員。作為構造函數,Map 也可以接受一個數組作為參數。該數組的成員是一個個表示鍵值對的數組。
const map = new Map([
[‘name‘, ‘張三‘],
[‘title‘, ‘Author‘]
]);
map.size // 2
map.has(‘name‘) // true
map.get(‘name‘) // "張三"
map.has(‘title‘) // true
map.get(‘title‘) // "Author"
上面代碼在新建 Map 實例時,就指定了兩個鍵name和title。
Map構造函數接受數組作為參數,實際上執行的是下面的算法。
const items = [
[‘name‘, ‘張三‘],
[‘title‘, ‘Author‘]
];
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);
Map實例的屬性和操作方法
(1)size屬性返回 Map 結構的成員總數;(2)set(key, value);(3)get(key);(4)has(key);(5)delete(key);(6)clear()
遍歷方法:
Map 結構原生提供三個遍歷器生成函數和一個遍歷方法。
keys():返回鍵名的遍歷器。values():返回鍵值的遍歷器。entries():返回所有成員的遍歷器。forEach():遍歷 Map 的所有成員。需要特別註意的是,Map 的遍歷順序就是插入順序
4、WeakMap
WeakMap與Map的區別有兩點。
首先,WeakMap只接受對象作為鍵名(null除外),不接受其他類型的值作為鍵名。其次,WeakMap的鍵名所指向的對象,不計入垃圾回收機制。
WeakMap 的語法
WeakMap 與 Map 在 API 上的區別主要是兩個,一是沒有遍歷操作(即沒有key()、values()和entries()方法),也沒有size屬性。因為沒有辦法列出所有鍵名,某個鍵名是否存在完全不可預測,跟垃圾回收機制是否運行相關。這一刻可以取到鍵名,下一刻垃圾回收機制突然運行了,這個鍵名就沒了,為了防止出現不確定性,就統一規定不能取到鍵名。二是無法清空,即不支持clear方法。因此,WeakMap只有四個方法可用:get()、set()、has()、delete()。
十一、Proxies
Proxy可以監聽對象身上發生了什麽事情,並在這些事情發生後執行一些相應的操作。一下子讓我們對一個對象有了很強的追蹤能力,同時在數據綁定方面也很有用處。Proxy 用於修改某些操作的默認行為,等同於在語言層面做出修改,即對編程語言進行編程。Proxy 可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裏表示由它來“代理”某些操作,可以譯為“代理器”。 ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。
var proxy = new Proxy(target, handler);
Proxy 對象的所有用法,都是上面這種形式,不同的只是handler參數的寫法。其中,new Proxy()表示生成一個Proxy實例,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定制攔截行為。
1、get()
get方法用於攔截某個屬性的讀取操作。上文已經有一個例子,下面是另一個攔截讀取操作的例子。
var person = {
name: "張三"
};
var proxy = new Proxy(person, {
get: function(target, property) {
if (property in target) {
return target[property];
} else {
throw new ReferenceError("Property \"" + property + "\" does not exist.");
}
}
});
proxy.name // "張三"
proxy.age // 拋出一個錯誤
上面代碼表示,如果訪問目標對象不存在的屬性,會拋出一個錯誤。如果沒有這個攔截函數,訪問不存在的屬性,只會返回undefined。
2、set()
set方法用來攔截某個屬性的賦值操作。假定Person對象有一個age屬性,該屬性應該是一個不大於200的整數,那麽可以使用Proxy保證age的屬性值符合要求。
let validator = {
set: function(obj, prop, value) {
if (prop === ‘age‘) {
if (!Number.isInteger(value)) {
throw new TypeError(‘The age is not an integer‘);
}
if (value > 200) {
throw new RangeError(‘The age seems invalid‘);
}
}
// 對於age以外的屬性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = ‘young‘ // 報錯
person.age = 300 // 報錯
上面代碼中,由於設置了存值函數set,任何不符合要求的age屬性賦值,都會拋出一個錯誤,這是數據驗證的一種實現方法。利用set方法,還可以數據綁定,即每當對象發生變化時,會自動更新 DOM。
十二、Symbols(講)
1、ES5 的對象屬性名都是字符串,這容易造成屬性名的沖突。比如,你使用了一個他人提供的對象,但又想為這個對象添加新的方法(mixin 模式),新方法的名字就有可能與現有方法產生沖突。如果有一種機制,保證每個屬性的名字都是獨一無二的就好了,這樣就從根本上防止屬性名的沖突。這就是 ES6 引入Symbol的原因。
ES6 引入了一種新的原始數據類型Symbol,表示獨一無二的值。它是 JavaScript 語言的第七種數據類型,前六種是:undefined、null、布爾值(Boolean)、字符串(String)、數值(Number)、對象(Object)。
Symbol 值通過Symbol函數生成。這就是說,對象的屬性名現在可以有兩種類型,一種是原來就有的字符串,另一種就是新增的 Symbol 類型。凡是屬性名屬於 Symbol 類型,就都是獨一無二的,可以保證不會與其他屬性名產生沖突。
let s = Symbol();
typeof s
// "symbol"
上面代碼中,變量s就是一個獨一無二的值。typeof運算符的結果,表明變量s是 Symbol 數據類型,而不是字符串之類的其他類型。註意,Symbol函數前不能使用new命令,否則會報錯。這是因為生成的 Symbol 是一個原始類型的值,不是對象。也就是說,由於 Symbol 值不是對象,所以不能添加屬性。基本上,它是一種類似於字符串的數據類型。
註意:Symbol 值不能與其他類型的值進行運算,會報錯。但是,Symbol 值可以顯式轉為字符串(String(sym))。另外,Symbol 值也可以轉為布爾值,但是不能轉為數值(Boolean(sym))。
作為屬性名的 Symbol
由於每一個 Symbol 值都是不相等的,這意味著 Symbol 值可以作為標識符,用於對象的屬性名,就能保證不會出現同名的屬性。這對於一個對象由多個模塊構成的情況非常有用,能防止某一個鍵被不小心改寫或覆蓋。
var mySymbol = Symbol();
// 第一種寫法
var a = {};
a[mySymbol] = ‘Hello!‘;
// 第二種寫法
var a = {
[mySymbol]: ‘Hello!‘
};
// 第三種寫法
var a = {};
Object.defineProperty(a, mySymbol, { value: ‘Hello!‘ });
// 以上寫法都得到同樣結果
a[mySymbol] // "Hello!"
上面代碼通過方括號結構和Object.defineProperty,將對象的屬性名指定為一個 Symbol 值。
註意,Symbol 值作為對象屬性名時,不能用點運算符。
var mySymbol = Symbol();
var a = {};
a.mySymbol = ‘Hello!‘;
a[mySymbol] // undefined
a[‘mySymbol‘] // "Hello!"
上面代碼中,因為點運算符後面總是字符串,所以不會讀取mySymbol作為標識名所指代的那個值,導致a的屬性名實際上是一個字符串,而不是一個 Symbol 值。(還有很多Symbol的方法和屬性)
十三、Math,Number,String,Object 的新API
對Math,Number,String還有Object等添加了許多新的API。下面代碼同樣來自es6features,對這些新API進行了簡單展示。
Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN("NaN") // false
Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2
"abcde".contains("cd") // true
"abc".repeat(3) // "abcabcabc"
Array.from(document.querySelectorAll(‘*‘)) // Returns a real Array
Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior
[0, 0, 0].fill(7, 1) // [0,7,7]
[1,2,3].findIndex(x => x == 2) // 1
["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // iterator 0, 1, 2
["a", "b", "c"].values() // iterator "a", "b", "c"
Object.assign(Point, { origin: new Point(0,0) })
十四、Promises
Promises是處理異步操作的一種模式,之前在很多三方庫中有實現,比如jQuery的deferred 對象。當你發起一個異步請求,並綁定了.when(), .done()等事件處理程序時,其實就是在應用promise模式。
//創建promise
var promise = new Promise(function(resolve, reject) {
// 進行一些異步或耗時操作
if ( /*如果成功 */ ) {
resolve("Stuff worked!");
} else {
reject(Error("It broke"));
}
});
//綁定處理程序
promise.then(function(result) {
//promise成功的話會執行這裏
console.log(result); // "Stuff worked!"
}, function(err) {
//promise失敗會執行這裏
console.log(err); // Error: "It broke"
});
十五、Generator 函數的語法
基本概念
Generator 函數是 ES6 提供的一種異步編程解決方案,語法行為與傳統函數完全不同。本章詳細介紹 Generator 函數的語法和 API,它的異步編程應用請看《Generator 函數的異步應用》一章。
Generator 函數有多種理解角度。從語法上,首先可以把它理解成,Generator 函數是一個狀態機,封裝了多個內部狀態。
執行 Generator 函數會返回一個遍歷器對象,也就是說,Generator 函數除了狀態機,還是一個遍歷器對象生成函數。返回的遍歷器對象,可以依次遍歷 Generator 函數內部的每一個狀態。
形式上,Generator 函數是一個普通函數,但是有兩個特征。一是,function關鍵字與函數名之間有一個星號;二是,函數體內部使用yield表達式,定義不同的內部狀態(yield在英語裏的意思就是“產出”)。
function* helloWorldGenerator() {
yield ‘hello‘;
yield ‘world‘;
return ‘ending‘;
}
var hw = helloWorldGenerator();
上面代碼定義了一個 Generator 函數helloWorldGenerator,它內部有兩個yield表達式(hello和world),即該函數有三個狀態:hello,world 和 return 語句(結束執行)。
然後,Generator 函數的調用方法與普通函數一樣,也是在函數名後面加上一對圓括號。不同的是,調用 Generator 函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象,也就是上一章介紹的遍歷器對象(Iterator Object)。
下一步,必須調用遍歷器對象的next方法,使得指針移向下一個狀態。也就是說,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)為止。換言之,Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法可以恢復執行。
hw.next()
// { value: ‘hello‘, done: false }
hw.next()
// { value: ‘world‘, done: false }
hw.next()
// { value: ‘ending‘, done: true }
hw.next()
// { value: undefined, done: true }
(背下來)上面代碼一共調用了四次next方法。
第一次調用,Generator 函數開始執行,直到遇到第一個yield表達式為止。next方法返回一個對象,它的value屬性就是當前yield表達式的值hello,done屬性的值false,表示遍歷還沒有結束。
第二次調用,Generator 函數從上次yield表達式停下的地方,一直執行到下一個yield表達式。next方法返回的對象的value屬性就是當前yield表達式的值world,done屬性的值false,表示遍歷還沒有結束。
第三次調用,Generator 函數從上次yield表達式停下的地方,一直執行到return語句(如果沒有return語句,就執行到函數結束)。next方法返回的對象的value屬性,就是緊跟在return語句後面的表達式的值(如果沒有return語句,則value屬性的值為undefined),done屬性的值true,表示遍歷已經結束。
第四次調用,此時 Generator 函數已經運行完畢,next方法返回對象的value屬性為undefined,done屬性為true。以後再調用next方法,返回的都是這個值。(由於 Generator 函數返回的遍歷器對象,只有調用next方法才會遍歷下一個內部狀態,所以其實提供了一種可以暫停執行的函數。yield表達式就是暫停標誌。遍歷器對象的next方法的運行邏輯如下。(1)遇到yield表達式,就暫停執行後面的操作,並將緊跟在yield後面的那個表達式的值,作為返回的對象的value屬性值。(2)下一次調用next方法時,再繼續往下執行,直到遇到下一個yield表達式。(3)如果沒有再遇到新的yield表達式,就一直運行到函數結束,直到return語句為止,並將return語句後面的表達式的值,作為返回的對象的value屬性值。(4)如果該函數沒有return語句,則返回的對象的value屬性值為undefined。)
總結一下,調用 Generator 函數,返回一個遍歷器對象,代表 Generator 函數的內部指針。以後,每次調用遍歷器對象的next方法,就會返回一個有著value和done兩個屬性的對象。value屬性表示當前的內部狀態的值,是yield表達式後面那個表達式的值;done屬性是一個布爾值,表示是否遍歷結束。
ES6 沒有規定,function關鍵字與函數名之間的星號,寫在哪個位置。這導致下面的寫法都能通過。
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
由於 Generator 函數仍然是普通函數,所以一般的寫法是上面的第三種,即星號緊跟在function關鍵字後面。本書也采用這種寫法。需要註意的是,yield表達式後面的表達式,只有當調用next方法、內部指針指向該語句時才會執行,因此等於為 JavaScript 提供了手動的“惰性求值”的語法功能。
function* gen() {
yield 123 + 456;
}
上面代碼中,yield後面的表達式123 + 456,不會立即求值,只會在next方法將指針移到這一句時,才會求值。yield表達式與return語句既有相似處,也有區別。相似之處在於,都能返回緊跟在語句後面的那個表達式的值。區別在於每次遇到yield,函數暫停執行,下一次再從該位置繼續向後執行,而return語句不具備位置記憶的功能。一個函數裏面,只能執行一次(或者說一個)return語句,但是可以執行多次(或者說多個)yield表達式。正常函數只能返回一個值,因為只能執行一次return;Generator 函數可以返回一系列的值,因為可以有任意多個yield。從另一個角度看,也可以說 Generator 生成了一系列的值,這也就是它的名稱的來歷。
function*foo(
x
){
var
y
=2
*
(
yield
(x
+1));
var
z
=yield
(y
/3);
return
(
x
+y
+z
);
}
vara
=foo(5);
a
.next() // Object{value:6, done:false}
a
.next() // Object{value:NaN, done:false}
a
.next() // Object{value:NaN, done:true}
varb
=foo(5);
b
.next() // { value:6, done:false }
b
.next(12) // { value:8, done:false }
b
.next(13) // { value:42, done:true }
(背下來)上面代碼中,第二次運行next方法的時候不帶參數,導致y的值等於2 * undefined(即NaN),除以3以後還是NaN,因此返回對象的value屬性也等於NaN。第三次運行Next方法的時候不帶參數,所以z等於undefined,返回對象的value屬性等於5 + NaN + undefined,即NaN。
如果向next方法提供參數,返回結果就完全不一樣了。上面代碼第一次調用b的next方法時,返回x+1的值6;第二次調用next方法,將上一次yield表達式的值設為12,因此y等於24,返回y / 3的值8;第三次調用next方法,將上一次yield表達式的值設為13,因此z等於13,這時x等於5,y等於24,所以return語句的值等於42。
註意,由於next方法的參數表示上一個yield表達式的返回值,所以在第一次使用next方法時,傳遞參數是無效的。V8引擎直接忽略第一次使用next方法時的參數,只有從第二次使用next方法開始,參數才是有效的。從語義上講,第一個next方法用來啟動遍歷器對象,所以不用帶有參數。
這個功能有很重要的語法意義。Generator 函數從暫停狀態到恢復運行,它的上下文狀態(context)是不變的。通過next方法的參數,就有辦法在Generator函數開始運行之後,繼續向函數體內部註入值。也就是說,可以在Generator 函數運行的不同階段,從外部向內部註入不同的值,從而調整函數為。
async 函數,它就是Generator函數的語法糖。
十六、Module 的語法(不會)
十七、Reflect
概述
Reflect對象與Proxy對象一樣,也是 ES6 為了操作對象而提供的新 API。Reflect對象的設計目的有這樣幾個。
(1) 將Object對象的一些明顯屬於語言內部的方法(比如Object.defineProperty),放到Reflect對象上。現階段,某些方法同時在Object和Reflect對象上部署,未來的新方法將只部署在Reflect對象上。也就是說,從Reflect對象上可以拿到語言內部的方法。
(2) 修改某些Object方法的返回結果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)在無法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)則會返回false。
// 老寫法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新寫法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
(3) 讓Object操作都變成函數行為。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函數行為。
// 老寫法
‘assign‘ in Object // true
// 新寫法
Reflect.has(Object, ‘assign‘) // true
(4)Reflect對象的方法與Proxy對象的方法一一對應,只要是Proxy對象的方法,就能在Reflect對象上找到對應的方法。這就讓Proxy對象可以方便地調用對應的Reflect方法,完成默認行為,作為修改行為的基礎。也就是說,不管Proxy怎麽修改默認行為,你總可以在Reflect上獲取默認行為。
Proxy(target, {
set: function(target, name, value, receiver) {
var success = Reflect.set(target,name, value, receiver);
if (success) {
log(‘property ‘ + name + ‘ on ‘ + target + ‘ set to ‘ + value);
}
return success;
}
});
上面代碼中,Proxy方法攔截target對象的屬性賦值行為。它采用Reflect.set方法將值賦值給對象的屬性,確保完成原有的行為,然後再部署額外的功能。
下面是另一個例子。
var loggedObj = new Proxy(obj, {
get(target, name) {
console.log(‘get‘, target, name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log(‘delete‘ + name);
return Reflect.deleteProperty(target, name);
},
has(target, name) {
console.log(‘has‘ + name);
return Reflect.has(target, name);
}
});
上面代碼中,每一個Proxy對象的攔截操作(get、delete、has),內部都調用對應的Reflect方法,保證原生行為能夠正常執行。添加的工作,就是將每一個操作輸出一行日誌。
有了Reflect對象以後,很多操作會更易讀。
// 老寫法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
// 新寫法
Reflect.apply(Math.floor, undefined, [1.75]) // 1
2、靜態方法
Reflect對象一共有13個靜態方法。
- Reflect.apply(target,thisArg,args)
- Reflect.construct(target,args)
- Reflect.get(target,name,receiver)
- Reflect.set(target,name,value,receiver)
- Reflect.defineProperty(target,name,desc)
- Reflect.deleteProperty(target,name)
- Reflect.has(target,name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
上面這些方法的作用,大部分與Object對象的同名方法的作用都是相同的,而且它與Proxy對象的方法是一一對應的。下面是對它們的解釋。
ES6瀏覽器支持情況
瀏覽器對於ES6的支持情況可以查看。kangax.github.io/es5-compat-table/es6/
檢測當前瀏覽器對ES6語法的支持情況可以查看。http://ruanyf.github.io/es-checker/index.cn.html
部署運行
1.有些瀏覽器已經大部分支持ES6的語法了。
2.babel
babel可以將ES6代碼轉換為ES5代碼,從而在現有環境中執行。babel官網:http://babeljs.io/ babel github地址:https://github.com/babel/babel
關於babel的使用可以參考阮一峰所寫的《Babel 入門教程》,網址為:http://www.ruanyifeng.com/blog/2016/01/babel.html
參考資料
1.阮一峰《ES6入門》
ES6(簡介及常用)-下