迭代器的概念
迭代器就是一種接口音比,為各種不同的數(shù)據(jù)結(jié)構(gòu)提供統(tǒng)一的訪問機(jī)制隧哮。任何的數(shù)據(jù)結(jié)構(gòu)只要部署了Inerator接口座舍,就可以完成遍歷操作(即依次處理該數(shù)據(jù)結(jié)構(gòu)的所有成員)沮翔,所有的迭代器都有一個next
方法,該方法會返回一個對象鉴竭,表示當(dāng)前數(shù)據(jù)成員的信息岸浑。這個對象具有value
和done
兩個屬性矢洲,value
屬性返回當(dāng)前位置的成員缩焦,done
屬性是一個布爾值,表示遍歷是否結(jié)束盖桥,即是否還有必要再一次調(diào)用next
方法揩徊。
迭代器的主要作用有三個:
- 為各種數(shù)據(jù)結(jié)構(gòu)提供一個統(tǒng)一的嵌赠、簡便的訪問接口;
- 使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按某種次序排列齿税;
- ES6創(chuàng)造了一種新的遍歷命令
for...of
循環(huán)凌箕,Inerator接口主要供for...of
消費(fèi)
迭代器的遍歷過程是這樣的:
- 創(chuàng)建一個指針對象词渤,指向當(dāng)前數(shù)據(jù)結(jié)構(gòu)的起始位置掖肋。
- 第一次調(diào)用指針對象的
next
方法,可以將指針指向數(shù)據(jù)結(jié)構(gòu)的第一個成員 - 第二次調(diào)用指針對象的
next
方法沿盅,可以將指針指向數(shù)據(jù)結(jié)構(gòu)的第二個成員 - 不斷調(diào)用指針對象的
next
方法,直到它指向數(shù)據(jù)結(jié)構(gòu)的結(jié)束位置
每一次調(diào)用next
方法韧掩,都會返回?cái)?shù)據(jù)結(jié)構(gòu)的當(dāng)前成員信息疗锐,具體來說费彼,就是返回一個包含value
和done
兩個屬性的對象,其中value
是當(dāng)前成員的值雇卷,而done
是一個布爾值关划,表示遍歷是否結(jié)束翘瓮。
var it = makeIterator(['1', '2','3']);
it.next() // { value: "1", done: false }
it.next() // { value: "2", done: false }
it.next() // { value: '3', done: false }
it.next() // { value: undefined, done: true}
it.next() // { value: undefined, done: true}
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
如果是使用TypeScript的寫法,迭代器接口(Iterable)调榄、指針對象(Iterator)和next
方法返回值的規(guī)格如下:
interface Iterable {
[Symbol.iterator]() : Iterator,
}
interface Iterator {
next(value?: any) : IterationResult,
}
interface IterationResult {
value: any,
done: boolean,
}
默認(rèn)Iterator接口
一種數(shù)據(jù)結(jié)構(gòu)只要部署了Iterator接口振峻,我們就稱這種數(shù)據(jù)結(jié)構(gòu)是“可遍歷的”择份,默認(rèn)的Iterator接口部署在數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator
屬性荣赶,或者說一個數(shù)據(jù)結(jié)構(gòu)只要具有Symbol.iterator
屬性,就可以認(rèn)為是可遍歷的利诺。
原生具備Iterator接口的數(shù)據(jù)結(jié)構(gòu)如下:
Array
/Map
/Set
/String
/TypedArray
/函數(shù)的arguments
對象/NodeList
對象
實(shí)例(數(shù)組的Symbol.iterator
屬性)
let arr = ['1','2','3'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: "1", done: false }
iter.next() // { value: "2", done: false }
iter.next() // { value: '3', done: false }
iter.next() // { value: undefined, done: true}
iter.next() // { value: undefined, done: true}
上面的代碼中慢逾,變量arr
是一個數(shù)組,原生就具有遍歷器接口口注,部署在arr
的Symbol.iterator
屬性上面寝志,所以調(diào)用這個屬性策添,便可以得到迭代器對象。
調(diào)用Iterator接口的場合
-
解構(gòu)賦值
對數(shù)組和
Set
結(jié)構(gòu)進(jìn)行解構(gòu)賦值時乐导,會默認(rèn)調(diào)用Symbol.iterator
方法let set = new Set().add('a').add('b').add('c'); let [x,y] = set; // x='a'; y='b' let [first, ...rest] = set; // first='a'; rest=['b','c'];
-
擴(kuò)展運(yùn)算符
let arr = ['b', 'c']; ['a', ...arr, 'd'] // ['a', 'b', 'c', 'd']
-
yield*
yield*
后面跟的是一個可遍歷的結(jié)構(gòu)兽叮,它會調(diào)用該結(jié)構(gòu)的遍歷器接口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 }
-
其他場合
由于數(shù)組的遍歷會調(diào)用遍歷器接口猾愿,所以任何接受數(shù)組為參數(shù)的場合账阻,其實(shí)都調(diào)用了遍歷器接口淘太,如:
- for...of
- Array.from()
- Map(), Set(), WeakMap(), WeakSet()(比如
new Map([['a',1],['b',2]])
) - Promise.all()
- Promise.race()
Iterator接口與Generator函數(shù)
Symbol.iterator
方法的最簡單實(shí)現(xiàn)蒲牧,還是使用生成器函數(shù)(Generator)
var arr = {};
arr[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...arr] //[1,2,3]
let obj = {
* [Symbol.iterator](){
yield 'hello';
yield 'world';
}
}
for(let x of obj) {
console.log(x)
}
上述實(shí)例中冰抢,Symbol.iterator
方法幾乎不用部署任何代碼,只要用yield命令給出每一步的返回值即可翠订。
迭代器對象的return(), throw()
迭代器除了有next
方法尽超,還可以具有return
和throw
方法梧躺,如果自定義迭代器對象生成函數(shù),那么next
方法是必須部署的巩踏。
return
方法的使用場合是蛀缝,如果for...of
循環(huán)提前退出(通常是因?yàn)槌鲥e屈梁,或者有break
語句或continue
語句),就會調(diào)用return
方法煞抬。如果一個對象在完成遍歷前革答,需要清理或釋放資源曙强,就可以部署return
方法碟嘴。
function readLinesSync(file) {
return {
next() {
return { done: false };
},
return() {
file.close();
return { done: true };
},
};
}
上面代碼中娜扇,函數(shù)readLinesSync
接受一個文件對象作為參數(shù),返回一個遍歷器對象枢析,其中除了next
方法刃麸,還部署了return
方法嫌蚤。下面的三種情況脱吱,都會觸發(fā)執(zhí)行return
方法。
// 情況一
for (let line of readLinesSync(fileName)) {
console.log(line);
break;
}
// 情況二
for (let line of readLinesSync(fileName)) {
console.log(line);
continue;
}
// 情況三
for (let line of readLinesSync(fileName)) {
console.log(line);
throw new Error();
}
上面代碼中续捂,情況一輸出文件的第一行以后牙瓢,就會執(zhí)行return
方法矾克,關(guān)閉這個文件;情況二輸出所有行以后酒繁,執(zhí)行return
方法州袒,關(guān)閉該文件弓候;情況三會在執(zhí)行return
方法關(guān)閉文件之后菇存,再拋出錯誤。
注意陈惰,return
方法必須返回一個對象,這是 Generator 規(guī)格決定的关筒。