W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
可迭代(Iterable) 對(duì)象是數(shù)組的泛化。這個(gè)概念是說(shuō)任何對(duì)象都可以被定制為可在 ?for..of
? 循環(huán)中使用的對(duì)象。
數(shù)組是可迭代的。但不僅僅是數(shù)組。很多其他內(nèi)建對(duì)象也都是可迭代的。例如字符串也是可迭代的。
如果從技術(shù)上講,對(duì)象不是數(shù)組,而是表示某物的集合(列表,集合),for..of
是一個(gè)能夠遍歷它的很好的語(yǔ)法,因此,讓我們來(lái)看看如何使其發(fā)揮作用。
通過(guò)自己創(chuàng)建一個(gè)對(duì)象,我們就可以輕松地掌握可迭代的概念。
例如,我們有一個(gè)對(duì)象,它并不是數(shù)組,但是看上去很適合使用 for..of
循環(huán)。
比如一個(gè) range
對(duì)象,它代表了一個(gè)數(shù)字區(qū)間:
let range = {
from: 1,
to: 5
};
// 我們希望 for..of 這樣運(yùn)行:
// for(let num of range) ... num=1,2,3,4,5
為了讓 range
對(duì)象可迭代(也就讓 for..of
可以運(yùn)行)我們需要為對(duì)象添加一個(gè)名為 Symbol.iterator
的方法(一個(gè)專(zhuān)門(mén)用于使對(duì)象可迭代的內(nèi)建 symbol)。
for..of
? 循環(huán)啟動(dòng)時(shí),它會(huì)調(diào)用這個(gè)方法(如果沒(méi)找到,就會(huì)報(bào)錯(cuò))。這個(gè)方法必須返回一個(gè) 迭代器(iterator) —— 一個(gè)有 ?next
? 方法的對(duì)象。for..of
? 僅適用于這個(gè)被返回的對(duì)象。for..of
? 循環(huán)希望取得下一個(gè)數(shù)值,它就調(diào)用這個(gè)對(duì)象的 ?next()
? 方法。next()
? 方法返回的結(jié)果的格式必須是 ?{done: Boolean, value: any}
?,當(dāng) ?done=true
? 時(shí),表示循環(huán)結(jié)束,否則 ?value
? 是下一個(gè)值。這是帶有注釋的 range
的完整實(shí)現(xiàn):
let range = {
from: 1,
to: 5
};
// 1. for..of 調(diào)用首先會(huì)調(diào)用這個(gè):
range[Symbol.iterator] = function() {
// ……它返回迭代器對(duì)象(iterator object):
// 2. 接下來(lái),for..of 僅與下面的迭代器對(duì)象一起工作,要求它提供下一個(gè)值
return {
current: this.from,
last: this.to,
// 3. next() 在 for..of 的每一輪循環(huán)迭代中被調(diào)用
next() {
// 4. 它將會(huì)返回 {done:.., value :...} 格式的對(duì)象
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
// 現(xiàn)在它可以運(yùn)行了!
for (let num of range) {
alert(num); // 1, 然后是 2, 3, 4, 5
}
請(qǐng)注意可迭代對(duì)象的核心功能:關(guān)注點(diǎn)分離。
range
? 自身沒(méi)有 ?next()
? 方法。range[Symbol.iterator]()
? 創(chuàng)建了另一個(gè)對(duì)象,即所謂的“迭代器”對(duì)象,并且它的 ?next
? 會(huì)為迭代生成值。因此,迭代器對(duì)象和與其進(jìn)行迭代的對(duì)象是分開(kāi)的。
從技術(shù)上說(shuō),我們可以將它們合并,并使用 ?range
? 自身作為迭代器來(lái)簡(jiǎn)化代碼。
就像這樣:
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
this.current = this.from;
return this;
},
next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
for (let num of range) {
alert(num); // 1, 然后是 2, 3, 4, 5
}
現(xiàn)在 range[Symbol.iterator]()
返回的是 range
對(duì)象自身:它包括了必需的 next()
方法,并通過(guò) this.current
記憶了當(dāng)前的迭代進(jìn)程。這樣更短,對(duì)嗎?是的。有時(shí)這樣也可以。
但缺點(diǎn)是,現(xiàn)在不可能同時(shí)在對(duì)象上運(yùn)行兩個(gè) for..of
循環(huán)了:它們將共享迭代狀態(tài),因?yàn)橹挥幸粋€(gè)迭代器,即對(duì)象本身。但是兩個(gè)并行的 for..of
是很罕見(jiàn)的,即使在異步情況下。
無(wú)窮迭代器(iterator)
無(wú)窮迭代器也是可能的。例如,將
range
設(shè)置為range.to = Infinity
,這時(shí)range
則成為了無(wú)窮迭代器。或者我們可以創(chuàng)建一個(gè)可迭代對(duì)象,它生成一個(gè)無(wú)窮偽隨機(jī)數(shù)序列。也是可能的。
next
沒(méi)有什么限制,它可以返回越來(lái)越多的值,這是正常的。
當(dāng)然,迭代這種對(duì)象的
for..of
循環(huán)將不會(huì)停止。但是我們可以通過(guò)使用break
來(lái)停止它。
數(shù)組和字符串是使用最廣泛的內(nèi)建可迭代對(duì)象。
對(duì)于一個(gè)字符串,for..of
遍歷它的每個(gè)字符:
for (let char of "test") {
// 觸發(fā) 4 次,每個(gè)字符一次
alert( char ); // t, then e, then s, then t
}
對(duì)于代理對(duì)(surrogate pairs),它也能正常工作!(譯注:這里的代理對(duì)也就指的是 UTF-16 的擴(kuò)展字符)
let str = '';
for (let char of str) {
alert( char ); // ,然后是
}
為了更深層地了解底層知識(shí),讓我們來(lái)看看如何顯式地使用迭代器。
我們將會(huì)采用與 ?for..of
? 完全相同的方式遍歷字符串,但使用的是直接調(diào)用。這段代碼創(chuàng)建了一個(gè)字符串迭代器,并“手動(dòng)”從中獲取值。
let str = "Hello";
// 和 for..of 做相同的事
// for (let char of str) alert(char);
let iterator = str[Symbol.iterator]();
while (true) {
let result = iterator.next();
if (result.done) break;
alert(result.value); // 一個(gè)接一個(gè)地輸出字符
}
很少需要我們這樣做,但是比 for..of
給了我們更多的控制權(quán)。例如,我們可以拆分迭代過(guò)程:迭代一部分,然后停止,做一些其他處理,然后再恢復(fù)迭代。
這兩個(gè)官方術(shù)語(yǔ)看起來(lái)差不多,但其實(shí)大不相同。請(qǐng)確保你能夠充分理解它們的含義,以免造成混淆。
Symbol.iterator
? 方法的對(duì)象。length
? 屬性的對(duì)象,所以它們看起來(lái)很像數(shù)組。當(dāng)我們將 JavaScript 用于編寫(xiě)在瀏覽器或任何其他環(huán)境中的實(shí)際任務(wù)時(shí),我們可能會(huì)遇到可迭代對(duì)象或類(lèi)數(shù)組對(duì)象,或兩者兼有。
例如,字符串即是可迭代的(for..of
對(duì)它們有效),又是類(lèi)數(shù)組的(它們有數(shù)值索引和 length
屬性)。
但是一個(gè)可迭代對(duì)象也許不是類(lèi)數(shù)組對(duì)象。反之亦然,類(lèi)數(shù)組對(duì)象可能不可迭代。
例如,上面例子中的 range
是可迭代的,但并非類(lèi)數(shù)組對(duì)象,因?yàn)樗鼪](méi)有索引屬性,也沒(méi)有 length
屬性。
下面這個(gè)對(duì)象則是類(lèi)數(shù)組的,但是不可迭代:
let arrayLike = { // 有索引和 length 屬性 => 類(lèi)數(shù)組對(duì)象
0: "Hello",
1: "World",
length: 2
};
// Error (no Symbol.iterator)
for (let item of arrayLike) {}
可迭代對(duì)象和類(lèi)數(shù)組對(duì)象通常都 不是數(shù)組,它們沒(méi)有 push
和 pop
等方法。如果我們有一個(gè)這樣的對(duì)象,并想像數(shù)組那樣操作它,那就非常不方便。例如,我們想使用數(shù)組方法操作 range
,應(yīng)該如何實(shí)現(xiàn)呢?
有一個(gè)全局方法 Array.from 可以接受一個(gè)可迭代或類(lèi)數(shù)組的值,并從中獲取一個(gè)“真正的”數(shù)組。然后我們就可以對(duì)其調(diào)用數(shù)組方法了。
例如:
let arrayLike = {
0: "Hello",
1: "World",
length: 2
};
let arr = Array.from(arrayLike); // (*)
alert(arr.pop()); // World(pop 方法有效)
在 (*)
行的 Array.from
方法接受對(duì)象,檢查它是一個(gè)可迭代對(duì)象或類(lèi)數(shù)組對(duì)象,然后創(chuàng)建一個(gè)新數(shù)組,并將該對(duì)象的所有元素復(fù)制到這個(gè)新數(shù)組。
如果是可迭代對(duì)象,也是同樣:
// 假設(shè) range 來(lái)自上文的例子中
let arr = Array.from(range);
alert(arr); // 1,2,3,4,5 (數(shù)組的 toString 轉(zhuǎn)化方法生效)
Array.from
的完整語(yǔ)法允許我們提供一個(gè)可選的“映射(mapping)”函數(shù):
Array.from(obj[, mapFn, thisArg])
可選的第二個(gè)參數(shù) mapFn
可以是一個(gè)函數(shù),該函數(shù)會(huì)在對(duì)象中的元素被添加到數(shù)組前,被應(yīng)用于每個(gè)元素,此外 thisArg
允許我們?yōu)樵摵瘮?shù)設(shè)置 this
。
例如:
// 假設(shè) range 來(lái)自上文例子中
// 求每個(gè)數(shù)的平方
let arr = Array.from(range, num => num * num);
alert(arr); // 1,4,9,16,25
現(xiàn)在我們用 Array.from
將一個(gè)字符串轉(zhuǎn)換為單個(gè)字符的數(shù)組:
let str = '';
// 將 str 拆分為字符數(shù)組
let chars = Array.from(str);
alert(chars[0]); //
alert(chars[1]); //
alert(chars.length); // 2
與 str.split
方法不同,它依賴于字符串的可迭代特性。因此,就像 for..of
一樣,可以正確地處理代理對(duì)(surrogate pair)。(譯注:代理對(duì)也就是 UTF-16 擴(kuò)展字符。)
技術(shù)上來(lái)講,它和下面這段代碼做的是相同的事:
let str = '';
let chars = []; // Array.from 內(nèi)部執(zhí)行相同的循環(huán)
for (let char of str) {
chars.push(char);
}
alert(chars);
……但 Array.from
精簡(jiǎn)很多。
我們甚至可以基于 Array.from
創(chuàng)建代理感知(surrogate-aware)的slice
方法(譯注:也就是能夠處理 UTF-16 擴(kuò)展字符的 slice
方法):
function slice(str, start, end) {
return Array.from(str).slice(start, end).join('');
}
let str = '';
alert( slice(str, 1, 3) ); //
// 原生方法不支持識(shí)別代理對(duì)(譯注:UTF-16 擴(kuò)展字符)
alert( str.slice(1, 3) ); // 亂碼(兩個(gè)不同 UTF-16 擴(kuò)展字符碎片拼接的結(jié)果)
可以應(yīng)用 for..of
的對(duì)象被稱(chēng)為 可迭代的。
Symbol.iterator
方法。obj[Symbol.iterator]()
? 的結(jié)果被稱(chēng)為 迭代器(iterator)。由它處理進(jìn)一步的迭代過(guò)程。next()
? 方法,它返回一個(gè) ?{done: Boolean, value: any}
? 對(duì)象,這里 ?done:true
? 表明迭代結(jié)束,否則 ?value
? 就是下一個(gè)值。Symbol.iterator
? 方法會(huì)被 ?for..of
? 自動(dòng)調(diào)用,但我們也可以直接調(diào)用它。
Symbol.iterator
?。
有索引屬性和 length
屬性的對(duì)象被稱(chēng)為 類(lèi)數(shù)組對(duì)象。這種對(duì)象可能還具有其他屬性和方法,但是沒(méi)有數(shù)組的內(nèi)建方法。
如果我們仔細(xì)研究一下規(guī)范 —— 就會(huì)發(fā)現(xiàn)大多數(shù)內(nèi)建方法都假設(shè)它們需要處理的是可迭代對(duì)象或者類(lèi)數(shù)組對(duì)象,而不是“真正的”數(shù)組,因?yàn)檫@樣抽象度更高。
Array.from(obj[, mapFn, thisArg])
將可迭代對(duì)象或類(lèi)數(shù)組對(duì)象 obj
轉(zhuǎn)化為真正的數(shù)組 Array
,然后我們就可以對(duì)它應(yīng)用數(shù)組的方法??蛇x參數(shù) mapFn
和 thisArg
允許我們將函數(shù)應(yīng)用到每個(gè)元素。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: