by @zhangbao(zhangbao) #0104
可以將可迭代對象理解為“寬泛意義上的數組”——就是說,不一定是數組(Array.isArray(iterable)
返回 false
),但卻能夠被 for...of
循環遍歷。
概覽
- 可迭代對象不一定是數組,數組一定是可迭代對象。
- 每個可迭代對象必然包含一個
[Symbol.iterator]
方法屬性 - 字符串也是可迭代對象

改造普通對象
我們舉一個例子,下面有一個對象:
let range = {
from: 1,
to: 5
};
// 我們想用 for..of 遍歷 range,得到從 1(from) 到 5(to) 的自然數
// for(let num of range) { consol.log(num) } // 遍歷結果 1 -> 2 -> 3 -> 4 -> 5
明眼人一看,就知道 range
不就是個普通對象嘛,跟可迭代對象有什麼關係呢?還要用 for..of
遍歷,遍歷結果還要是 1
-> 2
-> 3
-> 4
-> 5
,這不扯淡呢嗎?說的對,現在肯定是扯淡,那是因為我們啥都沒做呢,可不是扯淡嗎?
為了能讓 range
這個普通對象變為可迭代對象,我們需要先來了解下 Symbol.iterator
這個系統預置變量。
Symbol.iterator
我們先來看下,這個屬性的描述是怎樣的。
Object.getOwnPropertyDescriptor(Symbol, 'iterator')
// {
// value: Symbol(Symbol.iterator),
// writable: false,
// enumerable: false,
// configurable: false
// }
由結果可知,這個屬性我們是修改不了的。
下面我們為上面的 range
對象添加一個 [Symbol.iterator]
方法屬性——對的,這個屬性的屬性值是函數,每個可迭代對象都有一個 [Symbol.iterator]
方法屬性,沒有的話,肯定不是可迭代對象。
let range = {
from: 1,
to: 5
}
// 1. for..of 循環首先會調用對象上的 [Symbol.iterator] 屬性——range[Symbol.iterator](),
// 屬性 range[Symbol.iterator] 稱為“迭代對象生成器”或“迭代對象生成函數”
range[Symbol.iterator] = function() {
// range[Symbol.iterator]() 的調用結果,會返回一個包含 next 方法的對象,
// 這個對象稱為“迭代對象”
// 2. 接下來, for..of 就是完全在跟這個迭代對象打交道了
return {
current: this.from,
last: this.to,
// 3. 每次 for..of 循環一次,就要調用一次 next 方法
next() {
// 4. 從 next 方法返回的對象中,我們能獲得當前遍歷的值(value)以及遍歷是否結束的標記(done)
if (this.current <= this.last) {
return { done: false, value: this.current++ }
} else {
return { done: true }
}
}
}
}
for...of
循環遍歷的本質是:
for..of
循環首先會調用對象上的方法屬性[Symbol.iterator]
——range[Symbol.iterator]()
,得到一個包含next
方法的對象。
- 這個包含
next
方法的對象稱為迭代對象(iterator object) - 屬性
range[Symbol.iterator]
被稱為迭代對象生成器或迭代對象生成函數
- 接下來,
for..of
就是完全在跟這個迭代對象打交道了, - 每次
for..of
循環一次,就要調用一次next
方法, - 從
next
方法返回的對象中({ done: ..., value: ... }
),我們能獲得當前遍歷的值(value
)以及遍歷是否結束的標記(done
)。
經過上面的敘述,我們還可以將可迭代對象定義為:能夠生成“迭代對象”的對象。
**
遍歷改造對象
現在對改造後的 range
對象進行遍歷。
let range = {
from: 1,
to: 5
}
range[Symbol.iterator] = function() {...}
for (let num of range) {
console.log(num) // 1, 然後是 2, 3, 4, 5
}
很酷啊,現在可以一次遍歷出 1
-> 2
-> 3
-> 4
-> 5
這 5 個數字了。
手動遍歷可迭代對象
因為好玩,咱們模仿 for...of
循環內部執行流程,純手工寫一下遍歷可迭代對象的邏輯吧。
這裡會用到 while
循環:
// 下面的寫法,等同於
// for (let num of range) { console.log(num) };
let iterator = range[Symbol.iterator]();
while (true) {
let result = iterator.next();
// 標記結束(done 為 true),就終止循環,結束遍歷
if (result.done) break;
// 否則,打印當前遍歷的值
console.log(result.value);
}
- 首先,手動調用
range[Symbol.iterator]
方法,得迭代對象 - 在
while
循環內部: - 如果標記結束(
done
為true
),就終止循環,結束遍歷 - 否則,打印當前遍歷的值
內置可迭代對象
前面我們說過:可迭代對象不一定是數組,現在再加一句:數組一定是可迭代對象。根據經驗,我們知道數組是可以用 for...of
循環遍歷的。
數組

遍歷 symbols
被 for...of
循環成功遍歷了。這是意料之中的事情,但我們再來看下,這個數組對象裡是不是有個叫 Symbol.iterator
的屬性 🕵️♂️

果然!
字符串
字符串也是可迭代對象。證據如下:

再來找找 Symbol.iterator
屬性。

Map 和 Set
Map 和 Set 也是能夠被 for...of
遍歷的。在這裡就不多舉例了,直接展示它們各自部署的 Symbol.iterator
屬性。


從上面的截圖裡,我們可以總結出一點內容來:
- Map 對象默認的迭代對象生成器函數是
map.entries()
,而- Set 對象默認的迭代對象生成器函數是
map.values()
更多關於 Map 和 Set 對象的遍歷內容,請參考《遍歷 Map 和 Set》。
(完)