ES6學習筆記13 Iterator 和 for...of 迴圈
Iterator介面簡介
遍歷器(Iterator)是一種介面,為各種不同的資料結構提供統一的訪問機制。任何資料結構只要部署 Iterator 介面,就可以完成遍歷操作(即依次處理該資料結構的所有成員)。遍歷器介面可供for...of
使用,迴圈遍歷某種資料結構
Iterator 的遍歷過程:
(1)建立一個指標物件,指向當前資料結構的起始位置。也就是說,遍歷器物件本質上,就是一個指標物件。
(2)第一次呼叫指標物件的next方法,可以將指標指向資料結構的第一個成員。
(3)第二次呼叫指標物件的next方法,指標就指向資料結構的第二個成員。
(4)不斷呼叫指標物件的next方法,直到它指向資料結構的結束位置。
每一次呼叫next方法,都會返回資料結構的當前成員的資訊。具體來說,就是返回一個包含value
和done
兩個屬性的物件。其中,value
屬性是當前成員的值,done
屬性是一個布林值,表示遍歷是否結束。
下面是模擬next
方法返回值的例子
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator (array) {
var nextIndex = 0;
return { //返回一個具有next方法的遍歷器物件
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
ES6 規定,預設的 Iterator 介面部署在資料結構的Symbol.iterator
屬性,或者說,一個數據結構只要具有Symbol.iterator
Symbol.iterator
屬性本身是一個函式,就是當前資料結構預設的遍歷器生成函式。執行這個函式,就會返回一個遍歷器。至於屬性名Symbol.iterator
,它是一個表示式,返回Symbol
物件的iterator
屬性,這是一個預定義好的、型別為 Symbol
的特殊值,所以要放在方括號內
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
原生具備了Iterator
介面的資料結構有:Array,Map,Set,String,TypedArray,arguments物件,NodeList物件。除此之外,其他資料結構的Iterator介面都需要自己在Symbol.iterator屬性上面部署(或原型鏈上的物件具有該方法也可),這樣才會被for…of迴圈遍歷
下面是通過遍歷器實現指標結構的例子
function Obj(value) {
this.value = value;
this.next = null; //用於指向下一個物件
}
Obj.prototype[Symbol.iterator] = function() { //在原型物件上部署遍歷器介面
var iterator = { next: next }; //遍歷器為一個包含next方法的物件
var current = this; //指向當前物件
function next() {
if (current) { //如果當前物件存在,則返回值,並指向下一個物件
var value = current.value;
current = current.next;
return { done: false, value: value };
} else {
return { done: true };
}
}
return iterator;
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for (var i of one){
console.log(i); // 1, 2, 3
}
下面為物件部署Iterator介面
let obj = {
data: [ 'hello', 'world' ],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
};
} else {
return { value: undefined, done: true };
}
}
};
}
};
如果Symbol.iterator
方法對應的不是遍歷器生成函式(即會返回一個遍歷器物件),解釋引擎將會報錯。有了遍歷器介面,資料結構就可以用for...of
迴圈遍歷
Symbol.iterator
方法的最簡單實現,還是使用 Generator
函式。
let myIterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
}
[...myIterable] // [1, 2, 3]
// 或者採用下面的簡潔寫法
let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};
for (let x of obj) {
console.log(x);
}
// "hello"
// "world"
呼叫Iterator介面的場合
(1)解構賦值
對陣列和 Set 結構進行解構賦值時,會預設呼叫Symbol.iterator方法。
(2)擴充套件運算子
擴充套件運算子(…)也會呼叫預設的 Iterator 介面,可以將任何部署了 Iterator 介面的資料結構,轉為陣列。
(3)yield*
yield*
後面跟的是一個可遍歷的結構,它會呼叫該結構的遍歷器介面
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
(4)任何接受陣列作為引數的場合
for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
Promise.all()
Promise.race()
遍歷器物件的 return() 和 throw()
遍歷器物件除了具有next
方法,還可以具有return
方法和throw
方法。在自己部署的遍歷器物件生成函式中,next
方法是必須,而return
方法和throw
方法是可選的
return
方法的使用場合是,如果for...of
迴圈提前退出(通常是因為出錯,或者有break語句),就會呼叫return
方法。如果一個物件在完成遍歷前,需要清理或釋放資源,就可以部署return
方法。
function readLinesSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return { done: false };
},
return() { //關閉檔案
file.close();
return { done: true };
}
};
},
};
}
return
方法必須返回一個物件,這是 Generator 規格決定的。
遍歷器的throw
方法用於丟擲錯誤,主要配合 Generator
函式內部捕獲錯誤來使用,一般遍歷器物件用不到這個方法
for…of 迴圈
for...of
迴圈可以使用的範圍包括陣列、Set 和 Map 結構、某些類似陣列的物件(比如arguments
物件、DOM NodeList 物件)、後文的 Generator 物件,以及字串等任何部署了Symbol.iterator
屬性的資料結構。
還可以使用其遍歷計算生成的資料結構,例如keys()
,values()
,entries()
let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {
console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']
for...of
迴圈還有一個特點,就是會正確識別 32 位 UTF-16 字元。
for (let x of 'a\uD83D\uDC0A') {
console.log(x);
}
// 'a'
// '\uD83D\uDC0A'
對於沒有Iterator
介面的類似陣列物件結構,可以使用Array.from
方法將其轉為陣列,在使用for...of
遍歷
let arrayLike = { length: 2, 0: 'a', 1: 'b' };
// 報錯
for (let x of arrayLike) {
console.log(x);
}
// 正確
for (let x of Array.from(arrayLike)) {
console.log(x);
}
對於普通的物件, 可以使用Object.keys()
方法將物件的鍵名生成一個數組,然後再遍歷
for (var key of Object.keys(someObject)) {
console.log(key + ': ' + someObject[key]);
}
for...of
與其他遍歷語法比較
- 陣列提供內建的
forEach
方法可以遍歷陣列的成員,但是該方法中途無法跳出,break
和return
命令都無法湊效
myArray.forEach(function (value) {
console.log(value);
});
for...in
可以用來遍歷陣列和物件的鍵名,其無法直接遍歷鍵值,對於陣列來說,遍歷返回的值是索引的字串形式,例如”1”,”2”,”3”。除此之外其會遍歷手動新增的鍵名以及原型鏈上的鍵。在某些情況下,for...in
還會以任意順序遍歷鍵名
-