什麼是可迭代對象(Iterableobjects)?

NO IMAGE

by @zhangbao(zhangbao) #0104

可以將可迭代對象理解為“寬泛意義上的數組”——就是說,不一定是數組(Array.isArray(iterable) 返回 false),但卻能夠被 for...of 循環遍歷。

概覽

  • 可迭代對象不一定是數組,數組一定是可迭代對象。
  • 每個可迭代對象必然包含一個 [Symbol.iterator]  方法屬性
  • 字符串也是可迭代對象
什麼是可迭代對象(Iterableobjects)?

改造普通對象

我們舉一個例子,下面有一個對象:

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 循環遍歷的本質是:

  1. for..of 循環首先會調用對象上的方法屬性 [Symbol.iterator]——range[Symbol.iterator](),得到一個包含 next 方法的對象。
  • 這個包含 next 方法的對象稱為迭代對象(iterator object)
  • 屬性 range[Symbol.iterator] 被稱為迭代對象生成器迭代對象生成函數
  1. 接下來, for..of 就是完全在跟這個迭代對象打交道了,
  2. 每次 for..of 循環一次,就要調用一次 next 方法,
  3. 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);
}
  1. 首先,手動調用 range[Symbol.iterator] 方法,得迭代對象
  2. while 循環內部:
  3. 如果標記結束(donetrue),就終止循環,結束遍歷
  4. 否則,打印當前遍歷的值

內置可迭代對象

前面我們說過:可迭代對象不一定是數組,現在再加一句:數組一定是可迭代對象。根據經驗,我們知道數組是可以用 for...of 循環遍歷的。

數組

什麼是可迭代對象(Iterableobjects)?

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

什麼是可迭代對象(Iterableobjects)?

果然!

字符串

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

什麼是可迭代對象(Iterableobjects)?

再來找找 Symbol.iterator 屬性。

什麼是可迭代對象(Iterableobjects)?

Map 和 Set

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

什麼是可迭代對象(Iterableobjects)?
什麼是可迭代對象(Iterableobjects)?

從上面的截圖裡,我們可以總結出一點內容來:

  1. Map 對象默認的迭代對象生成器函數是 map.entries(),而
  2. Set 對象默認的迭代對象生成器函數是 map.values()

更多關於 Map 和 Set 對象的遍歷內容,請參考《遍歷 Map 和 Set》

(完)

相關文章

頭條面試官:你知道如何實現高性能版本的深拷貝嘛?

2019與我的自由啟蒙

從零搭建完整的React項目模板(Webpack+Reacthooks+Mobx+Antd)【演戲演全套】

格式化時間用了YYYYMMdd,元旦當天老闆喊我回去改Bug!