前言
日常開發(fā)經(jīng)常用到的遍歷,es6其中一章單獨討論了Iterator但對其概念很模糊屯阀,因此想深入研究下降狠。
基礎(chǔ)概念
Iterator 的作用有三個:
- 為各種數(shù)據(jù)結(jié)構(gòu),提供一個統(tǒng)一的溜畅、簡便的訪問接口捏卓;
- 使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按某種次序排列;
- ES6 創(chuàng)造了一種新的遍歷命令for...of循環(huán)慈格,Iterator 接口主要供for...of消費怠晴。
Iterator 的遍歷過程是這樣的。
- 創(chuàng)建一個指針對象浴捆,指向當(dāng)前數(shù)據(jù)結(jié)構(gòu)的起始位置蒜田。也就是說,遍歷器對象本質(zhì)上选泻,就是一個指針對象冲粤。
- 第一次調(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方法,都會返回數(shù)據(jù)結(jié)構(gòu)的當(dāng)前成員的信息忿族。具體來說锣笨,就是返回一個包含value和done兩個屬性的對象蝌矛。其中,value屬性是當(dāng)前成員的值错英,done屬性是一個布爾值入撒,表示遍歷是否結(jié)束。
// 模擬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: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
由于 Iterator 只是把接口規(guī)格加到數(shù)據(jù)結(jié)構(gòu)之上椭岩,所以茅逮,遍歷器與它所遍歷的那個數(shù)據(jù)結(jié)構(gòu),實際上是分開的判哥,完全可以寫出沒有對應(yīng)數(shù)據(jù)結(jié)構(gòu)的遍歷器對象,或者說用遍歷器對象模擬出數(shù)據(jù)結(jié)構(gòu)挺身。
默認 Iterator接口
ES6 規(guī)定章钾,默認的 Iterator 接口部署在數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator
屬性热芹,或者說伊脓,一個數(shù)據(jù)結(jié)構(gòu)只要具有Symbol.iterator
屬性,就可以認為是“可遍歷的”(iterable)椰棘。
Symbol.iterator
是一個當(dāng)前數(shù)據(jù)結(jié)構(gòu)默認的遍歷器生成函數(shù)邪狞,執(zhí)行這個函數(shù)茅撞,就會返回一個遍歷器。這是一個預(yù)定義好的剑令、類型為 Symbol 的特殊值拄查,所以要放在方括號內(nèi)。
const myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
class Collection {
*[Symbol.iterator]() {
let i = 0;
while(this[i] !== undefined) {
yield this[i];
++i;
}
}
}
let myCollection = new Collection();
myCollection[0] = 1;
myCollection[1] = 2;
for(let value of myCollection) {
console.log(value);
}
// 1
// 2
上述的案例中碍脏。function* (){}
為函數(shù)生成器,與普通函數(shù)的區(qū)別是通常中間用yeild作暫停執(zhí)行的標(biāo)記(類似return)役拴,可以通過next方法恢復(fù)執(zhí)行钾埂。
且a[mySymbol] = 'Hello!'
和let a = { [mySymbol]: 'Hello!' }
兩種方法都可以把Symbol添加為屬性名。
案例中實際為命名某Symbol屬性指向一個Generator函數(shù)用于遍歷淤击,即添加了遍歷器接口,在觸發(fā)循環(huán)遍歷時會被調(diào)用汞贸。
原生具備 Iterator 接口的數(shù)據(jù)結(jié)構(gòu):Array矢腻、Map多柑、Set竣灌、String、TypedArray及汉、函數(shù)的 arguments 對象屯烦、NodeList 對象驻龟。
Object之所以沒有默認部署 Iterator 接口,是因為對象的哪個屬性先遍歷类溢,哪個屬性后遍歷是不確定的露懒,需要開發(fā)者手動指定龟梦。本質(zhì)上窃躲,遍歷器是一種線性處理,對于任何非線性的數(shù)據(jù)結(jié)構(gòu)蒂窒,部署遍歷器接口躁倒,就等于部署一種線性轉(zhuǎn)換洒琢。不過秧秉,嚴格地說,對象部署遍歷器接口并不是很必要象迎,因為這時對象實際上被當(dāng)作 Map 結(jié)構(gòu)使用,ES5 沒有 Map 結(jié)構(gòu)呛踊,而 ES6 原生提供了砾淌。
調(diào)用 Iterator 接口的場合
上面的案例也涉及到了。
- 解構(gòu)賦值
-
...
擴展運算符 -
yield*
后面跟的是一個可遍歷的結(jié)構(gòu)谭网,它會調(diào)用該結(jié)構(gòu)的遍歷器接口。yield*
后面的 Generator 函數(shù)(沒有return語句時)愉择,等同于在 Generator 函數(shù)內(nèi)部锥涕,部署一個for...of循環(huán)。
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
- 其他場合
for...of
,Array.from()
窿春,Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
,Promise.all()
,Promise.race()
例如此類的遍歷方法
遍歷器對象的throw和return
遍歷器對象除了具有next
方法尺栖,還可以具有return
方法和throw
方法嫡纠。如果你自己寫遍歷器對象生成函數(shù),那么next
方法是必須部署的,return
方法和throw
方法是否部署是可選的除盏。
// 情況一
for (let line of readLinesSync(fileName)) {
console.log(line);
break;
}
// 情況二
for (let line of readLinesSync(fileName)) {
console.log(line);
throw new Error();
}
情況一輸出文件的第一行以后叉橱,就會執(zhí)行return方法,關(guān)閉這個文件者蠕;情況二會在執(zhí)行return方法關(guān)閉文件之后窃祝,再拋出錯誤。
Generator 規(guī)格踱侣,return
方法必須返回一個對象
// throw()是將yield表達式替換成一個throw語句粪小。
gen.throw(new Error('出錯了')); // Uncaught Error: 出錯了
// 相當(dāng)于將 let result = yield x + y
// 替換成 let result = throw(new Error('出錯了'));
// return()是將yield表達式替換成一個return語句。
gen.return(2); // Object {value: 2, done: true}
// 相當(dāng)于將 let result = yield x + y
// 替換成 let result = return 2;
因此上述行為符合抡句,先執(zhí)行內(nèi)部探膊,再中斷返回或拋出錯誤
心得總結(jié)
Iterator,其涉及到了Symbol和Generator的知識點待榔。從最基本的原生可遍歷對象開始入手逞壁,難點就在于理解函數(shù)生成器如何運作以及其與Iterator 接口關(guān)聯(lián)起來。
說實話現(xiàn)在日常業(yè)務(wù)開發(fā)很少涉及到去添加或重寫Iterator 接口锐锣。但是一些組件工具方法中腌闯,這是可以利用的“騷操作”??。