迭代器是一種設(shè)計(jì)模式驾窟,它是一個(gè)對(duì)象,它可以遍歷并選擇序列中的對(duì)象讯泣,而開(kāi)發(fā)人員不需要了解該序列的底層結(jié)構(gòu)纫普。迭代器通常被稱為“輕量級(jí)”對(duì)象,因?yàn)閯?chuàng)建它的代價(jià)小好渠。
ES6 規(guī)定昨稼,默認(rèn)的 Iterator 接口部署在數(shù)據(jù)結(jié)構(gòu)的 Symbol.iterator 屬性,或者說(shuō)拳锚,一個(gè)數(shù)據(jù)結(jié)構(gòu)只要具有Symbol.iterator屬性假栓,就可以認(rèn)為是“可遍歷的”(iterable)。Symbol.iterator 屬性本身是一個(gè)函數(shù)霍掺,就是當(dāng)前數(shù)據(jù)結(jié)構(gòu)默認(rèn)的遍歷器生成函數(shù)匾荆。
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
}
for...of 語(yǔ)句創(chuàng)建一個(gè)循環(huán)來(lái)迭代可迭代的對(duì)象拌蜘。在 ES6 中引入的 for...of 循環(huán),以替代 for...in 和 forEach() 牙丽,并支持新的迭代協(xié)議简卧。for...of 允許你遍歷 Arrays(數(shù)組), Strings(字符串), Maps(映射), Sets(集合)等可迭代的數(shù)據(jù)結(jié)構(gòu)等。
語(yǔ)法
for (variable of iterable) {
statement
}
variable:每個(gè)迭代的屬性值被分配給該變量烤芦。
iterable:一個(gè)具有可枚舉屬性并且可以迭代的對(duì)象举娩。
用例
我們來(lái)探討一些用例。
Arrays(數(shù)組)
Arrays(數(shù)組)就是類列表(list-like)對(duì)象构罗。數(shù)組原型上有各種方法铜涉,允許對(duì)其進(jìn)行操作,比如修改和遍歷等操作遂唧。下面手在一個(gè)數(shù)組上進(jìn)行的 for...of 操作:
// array-example.js
const iterable = ['mini', 'mani', 'mo'];
for (const value of iterable) {
console.log(value);
}
// Output:
// mini
// mani
// mo
Maps(映射)
Map 對(duì)象就是保存 key-value(鍵值) 對(duì)芙代。對(duì)象和原始值可以用作 key(鍵)或 value(值)。Map 對(duì)象根據(jù)其插入方式迭代元素盖彭。換句話說(shuō)纹烹, for...of 循環(huán)將為每次迭代返回一個(gè) key-value(鍵值) 數(shù)組。
// map-example.js
const iterable = new Map([['one', 1], ['two', 2]]);
for (const [key, value] of iterable) {
console.log(`Key: ${key} and Value: ${value}`);
}
// Output:
// Key: one and Value: 1
// Key: two and Value: 2
Set(集合)
Set(集合) 對(duì)象允許你存儲(chǔ)任何類型的唯一值谬泌,這些值可以是原始值或?qū)ο蟆?Set(集合) 對(duì)象只是值的集合滔韵。 Set(集合) 元素的迭代基于其插入順序。 Set(集合) 中的值只能發(fā)生一次掌实。如果您創(chuàng)建一個(gè)具有多個(gè)相同元素的 Set(集合) 陪蜻,那么它仍然被認(rèn)為是單個(gè)元素。
// set-example.js
const iterable = new Set([1, 1, 2, 2, 1]);
for (const value of iterable) {
console.log(value);
}
// Output:
// 1
// 2
盡管我們的 Set(集合) 有多個(gè) 1 和 2 贱鼻,但輸出的只有 1 和 2 宴卖。
String(字符串)
字符串用于以文本形式存儲(chǔ)數(shù)據(jù)。
// string-example.js
const iterable = 'javascript';
for (const value of iterable) {
console.log(value);
}
// Output:
// "j"
// "a"
// "v"
// "a"
// "s"
// "c"
// "r"
// "i"
// "p"
// "t"
這里邻悬,對(duì)字符串執(zhí)行迭代症昏,并打印出每個(gè)索引上的字符。
Arguments Object(參數(shù)對(duì)象)
把一個(gè)參數(shù)對(duì)象看作是一個(gè)類數(shù)組(array-like)對(duì)象父丰,并且對(duì)應(yīng)于傳遞給函數(shù)的參數(shù)肝谭。這是一個(gè)用例:
// arguments-example.js
function args() {
for (const arg of arguments) {
console.log(arg);
}
}
args('a', 'b', 'c');
// Output:
// a
// b
// c
你可能會(huì)想,發(fā)生了什么事?! 如前所述蛾扇,當(dāng)調(diào)用函數(shù)時(shí)攘烛,arguments 會(huì)接收傳入 args() 函數(shù)的任何參數(shù)。所以镀首,如果我們傳遞 20 個(gè)參數(shù)給 args() 函數(shù)坟漱,我們將打印出 20 個(gè)參數(shù)。
Generators(生成器)
生成器是一個(gè)函數(shù)更哄,它可以退出函數(shù)芋齿,稍后重新進(jìn)入函數(shù)腥寇。
// generator-example.js
function* generator(){
yield 1;
yield 2;
yield 3;
};
for (const g of generator()) {
console.log(g);
}
// Output:
// 1
// 2
// 3
function* 定義了一個(gè)生成器函數(shù),該函數(shù)返回生成器對(duì)象(Generator object)觅捆。
退出迭代
JavaScript 提供了四種已知的終止循環(huán)執(zhí)行的方法:break赦役、continue、return 和 throw惠拭。讓我們來(lái)看一個(gè)例子:
const iterable = ['mini', 'mani', 'mo'];
for (const value of iterable) {
console.log(value);
break;
}
// Output:
// mini
在這個(gè)例子中扩劝,我們使用 break 關(guān)鍵字在一次執(zhí)行后終止循環(huán),所以只有 mini 被打印出來(lái)职辅。
普通對(duì)象不可迭代
for...of 循環(huán)僅適用于迭代。 而普通對(duì)象不可迭代聂示。 我們來(lái)看一下:
const obj = { fname: 'foo', lname: 'bar' };
for (const value of obj) { // TypeError: obj[Symbol.iterator] is not a function
console.log(value);
}
在這里域携,我們定義了一個(gè)普通對(duì)象 obj ,并且當(dāng)我們嘗試 for...of 對(duì)其進(jìn)行操作時(shí)鱼喉,會(huì)報(bào)錯(cuò):TypeError: obj[Symbol.iterator] is not a function秀鞭。
我們可以通過(guò)將類數(shù)組(array-like)對(duì)象轉(zhuǎn)換為數(shù)組來(lái)繞過(guò)它。該對(duì)象將具有一個(gè) length 屬性扛禽,其元素必須可以被索引锋边。我們來(lái)看一個(gè)例子:
// object-example.js
const obj = { length: 3, 0: 'foo', 1: 'bar', 2: 'baz' };
const array = Array.from(obj);
for (const value of array) {
console.log(value);
}
// Output:
// foo
// bar
// baz
Array.from() 方法可以讓我通過(guò)類數(shù)組(array-like)或可迭代對(duì)象來(lái)創(chuàng)建一個(gè)新的 Array(數(shù)組) 實(shí)例。
For…in
for...in 循環(huán)將遍歷對(duì)象的所有可枚舉屬性编曼。
//for-in-example.js
Array.prototype.newArr = () => {};
Array.prototype.anotherNewArr = () => {};
const array = ['foo', 'bar', 'baz'];
for (const value in array) {
console.log(value);
}
// Outcome:
// 0
// 1
// 2
// newArr
// anotherNewArr
for...in 不僅枚舉上面的數(shù)組聲明豆巨,它還從構(gòu)造函數(shù)的原型中查找繼承的非枚舉屬性,在這個(gè)例子中掐场,newArr 和 anotherNewArr 也會(huì)打印出來(lái)往扔。
與其他遍歷語(yǔ)法的比較
以數(shù)組為例,JavaScript 提供多種遍歷語(yǔ)法熊户。最原始的寫(xiě)法就是for循環(huán)萍膛。
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}
這種寫(xiě)法比較麻煩,因此數(shù)組提供內(nèi)置的forEach方法嚷堡。
myArray.forEach(function (value) {
console.log(value);
});
這種寫(xiě)法的問(wèn)題在于蝗罗,無(wú)法中途跳出forEach循環(huán),break命令或return命令都不能奏效蝌戒。
for...in循環(huán)可以遍歷數(shù)組的鍵名串塑。
for (var index in myArray) {
console.log(myArray[index]);
}
for...in循環(huán)有幾個(gè)缺點(diǎn)。
數(shù)組的鍵名是數(shù)字瓶颠,但是for...in循環(huán)是以字符串作為鍵名“0”拟赊、“1”、“2”等等粹淋。
for...in循環(huán)不僅遍歷數(shù)字鍵名吸祟,還會(huì)遍歷手動(dòng)添加的其他鍵瑟慈,甚至包括原型鏈上的鍵。
某些情況下屋匕,for...in循環(huán)會(huì)以任意順序遍歷鍵名葛碧。
總之,for...in循環(huán)主要是為遍歷對(duì)象而設(shè)計(jì)的过吻,不適用于遍歷數(shù)組进泼。
for...of循環(huán)相比上面幾種做法,有一些顯著的優(yōu)點(diǎn)纤虽。
for (let value of myArray) {
console.log(value);
}
有著同for...in一樣的簡(jiǎn)潔語(yǔ)法乳绕,但是沒(méi)有for...in那些缺點(diǎn)。
不同于forEach方法逼纸,它可以與break洋措、continue和return配合使用。
提供了遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一操作接口杰刽。
下面是一個(gè)使用 break 語(yǔ)句菠发,跳出for...of循環(huán)的例子。
for (var n of fibonacci) {
if (n > 1000)
break;
console.log(n);
}
上面的例子贺嫂,會(huì)輸出斐波納契數(shù)列小于等于 1000 的項(xiàng)滓鸠。如果當(dāng)前項(xiàng)大于 1000,就會(huì)使用break語(yǔ)句跳出for...of循環(huán)第喳。