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