Iterator(遍歷器)
概念
表示"集合"的數(shù)據(jù)結(jié)構(gòu),主要是原有的Array
和Object
,ES6增加的Map
和Set
.需要一個(gè)統(tǒng)一的接口機(jī)制來(lái)處理所有不同的數(shù)據(jù)結(jié)構(gòu)
任何數(shù)據(jù)結(jié)構(gòu)只要部署Iterator接口,就可以完成遍歷操作(即依次處理該數(shù)據(jù)結(jié)構(gòu)的所有成員)
作用主要有三點(diǎn):
- 為各種數(shù)據(jù)結(jié)構(gòu)提供一個(gè)統(tǒng)一的,簡(jiǎn)便的訪問(wèn)接口
- 使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按照某種次序排列
- 方便使用
for...of
使用
遍歷原理主要是創(chuàng)建一個(gè)指針對(duì)象,指向當(dāng)前數(shù)據(jù)結(jié)構(gòu)的起始位置,不斷調(diào)用指針對(duì)象的next
方法,每次調(diào)用next
方法,都會(huì)返回?cái)?shù)據(jù)結(jié)構(gòu)的當(dāng)前成員的信息.具體來(lái)說(shuō)就是返回一個(gè)包含value
和done
兩個(gè)屬性的對(duì)象,其中,value
屬性是當(dāng)前成員的值,done
屬性是一個(gè)布爾值,表示遍歷是否結(jié)束
默認(rèn)Iterator接口
一個(gè)數(shù)據(jù)結(jié)構(gòu)只要部署了Iterator接口,我們就稱這種數(shù)據(jù)結(jié)構(gòu)是"可遍歷的"
ES6規(guī)定,默認(rèn)的Iterator接口部署在數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator
屬性,Symbol.iterator
熟悉本身就是一個(gè)函數(shù),就是當(dāng)前數(shù)據(jù)結(jié)構(gòu)默認(rèn)的遍歷器生成函數(shù).執(zhí)行這個(gè)函數(shù),就會(huì)返回一個(gè)遍歷器.至于屬性名Symbol.iterator
,它是一個(gè)表達(dá)式,返回Symbol
對(duì)象的iterator
屬性,這是一個(gè)預(yù)定義好的,類型為Symbol
的特殊值,所以要放在方括號(hào)內(nèi)
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
ES6的有些數(shù)據(jù)結(jié)構(gòu)原生具備Iterator接口,即不用任何處理,就可以被for...of
循環(huán)遍歷
原生具備Iterator接口的數(shù)據(jù)結(jié)構(gòu)如下:
- Array
- Map
- Set
- String
- TypedArray
- 函數(shù)的arguments對(duì)象
- NodeList對(duì)象
下面的例子是數(shù)組的Symbol.iterator
屬性
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
除了原生部署Iterator接口的數(shù)據(jù)結(jié)構(gòu)之外,其它數(shù)據(jù)結(jié)構(gòu)(主要是對(duì)象)的Iterator接口,都需要在Symbol.iterator
屬性上面部署
對(duì)象之所以沒有默認(rèn)部署,是因?yàn)閷?duì)象的哪個(gè)屬性先遍歷,哪個(gè)屬性后遍歷是不確定的.本質(zhì)上,遍歷器是一種線性處理,對(duì)于任何非線性的數(shù)據(jù)接口,都必須部署遍歷器接口,就等于部署一種線性轉(zhuǎn)換.(對(duì)象部署遍歷器接口不是很必要,因?yàn)榇藭r(shí)對(duì)象實(shí)際上被當(dāng)做Map結(jié)構(gòu)使用)
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
下面是為一個(gè)對(duì)象增加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 };
}
}
};
}
};
對(duì)于類似數(shù)組的對(duì)象(存在數(shù)值鍵名和length
屬性),部署Iterator接口,有一個(gè)簡(jiǎn)便方法,就是Symbol.iterator
方法直接引用數(shù)組的Iterator接口
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
[...document.querySelectorAll('div')] // 可以執(zhí)行了
注意,普通對(duì)象部署數(shù)組的Symbol.iterator
方法,并無(wú)效果
如果Symbol.iterator
方法對(duì)應(yīng)的不是遍歷器生成函數(shù),解釋引擎將會(huì)報(bào)錯(cuò)
調(diào)用Iterator接口的場(chǎng)合
有一些場(chǎng)合默認(rèn)調(diào)用Iterator接口
- 解構(gòu)賦值
對(duì)數(shù)組和Set解構(gòu)進(jìn)行解構(gòu)賦值時(shí) - 拓展運(yùn)算符
只要某個(gè)數(shù)據(jù)結(jié)構(gòu)部署了Iterator接口,就可以對(duì)它使用拓展運(yùn)算符,將其轉(zhuǎn)為數(shù)組 - yield*
- 其它場(chǎng)合
又要數(shù)組的遍歷會(huì)調(diào)用遍歷器接口,所以任何接受數(shù)組作為參數(shù)的場(chǎng)合,其實(shí)都調(diào)用了遍歷器接口- for...of
- Array.from
- Map,Set,WeakMap,WeakSet
- Promise.all
- Promise.race
Iterator接口與Generator函數(shù)
Symbol.iterator
方法的最簡(jiǎn)單實(shí)現(xiàn),還是使用Generator函數(shù)
let myIterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
}
[...myIterable] // [1, 2, 3]
// 或者采用下面的簡(jiǎn)潔寫法
let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};
for (let x of obj) {
console.log(x);
}
// "hello"
// "world"
遍歷器對(duì)象的return()和throw()
return
方法和throw
方法是否部署是可選的
return
方法的使用場(chǎng)合是,如果for...of
循環(huán)提前退出(通常是因?yàn)槌鲥e(cuò),或者有break
語(yǔ)句或continue
語(yǔ)句),就會(huì)調(diào)用return
方法.如果一個(gè)對(duì)象在完成遍歷前,需要清理或釋放資源,就可以部署return
方法
function readLinesSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return { done: false };
},
return() {
file.close();
return { done: true };
}
};
},
};
}
注意:return
方法必須返回一個(gè)對(duì)象
throw
方法主要配合Generator函數(shù)使用,一般的遍歷器對(duì)象用不到這個(gè)方法
for...of循環(huán)
概念
引入for...of
循環(huán)作為遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一方法
JavaScript原有的for...in
循環(huán),只能獲得對(duì)象的鍵名,不能直接獲取鍵值.ES6提供for...of
循環(huán),允許遍歷獲得鍵值
for...of
循環(huán)調(diào)用遍歷接口,數(shù)組的遍歷器接口只返回具有數(shù)字索引的屬性.這一點(diǎn)跟for...in
循環(huán)不一樣
Set和Map結(jié)構(gòu)
遍歷Set結(jié)構(gòu)和Map結(jié)構(gòu),注意的地方有兩點(diǎn):首先,遍歷的順序是按照各個(gè)成員被添加進(jìn)數(shù)據(jù)結(jié)構(gòu)的順序.其次,Set結(jié)構(gòu)遍歷時(shí),返回的是一個(gè)值,而Map結(jié)構(gòu)遍歷時(shí),返回的是一個(gè)數(shù)組
計(jì)算生成的數(shù)據(jù)結(jié)構(gòu)
ES6的數(shù)組,Set,Map都部署了以下三個(gè)方法,調(diào)用后都返回遍歷器對(duì)象
-
entries()
:遍歷器對(duì)象用來(lái)遍歷[鍵名,鍵值]
組成的數(shù)組.對(duì)于數(shù)組,鍵名就是索引值;對(duì)于Set,鍵名與鍵值相同;Map結(jié)構(gòu)的Iterator接口,默認(rèn)就是調(diào)用該方法. keys()
values()
類似數(shù)組的對(duì)象
對(duì)于字符串來(lái)說(shuō),for...of
有一個(gè)特點(diǎn),就是會(huì)正確識(shí)別32位UTF-16字符
可以使用Array.from
方法將類似數(shù)組的對(duì)象轉(zhuǎn)為數(shù)組
let arrayLike = { length: 2, 0: 'a', 1: 'b' };
// 報(bào)錯(cuò)
for (let x of arrayLike) {
console.log(x);
}
// 正確
for (let x of Array.from(arrayLike)) {
console.log(x);
}
對(duì)象
對(duì)于普通的對(duì)象,for...of
結(jié)構(gòu)不能直接使用,必須部署了Iterator接口后才能使用
一種解決方法是,使用Object.keys
方法將對(duì)象的鍵名生成一個(gè)數(shù)組
for (var key of Object.keys(someObject)) {
console.log(key + ': ' + someObject[key]);
}
另一個(gè)方法是使用Generator函數(shù)將對(duì)象重新包裝一下
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(obj)) {
console.log(key, '->', value);
}
// a -> 1
// b -> 2
// c -> 3
與其他遍歷語(yǔ)法的比較
forEach
無(wú)法中途跳出循環(huán)
for...in
有幾個(gè)缺點(diǎn):
1.以字符串作為鍵名"0","1","2"等
2.不僅遍歷數(shù)組鍵名,還會(huì)遍歷手動(dòng)添加的其他鍵,甚至包括原型鏈上的鍵
3.某些情況下,會(huì)以任意順序遍歷鍵名
for...of
有一些顯著的優(yōu)點(diǎn):
1.沒有for...in
那些缺點(diǎn)
2.不同于forEach
方法,它可以與break,continue,return
配合使用
3.提供了遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一操作接口