What makes JavaScript Iterables Iterable? - Iterator Protocol and Generator

什麼讓 JavaScript 資料結構能夠被迭代? 了解 Iterator Protocol 與 Generator

前言

你有想過為什麼陣列可以被迭代處理(for...of)但物件不行嗎?JavaScript Iterables (可迭代物件)背後仰賴 Iterator Protocol 來實踐可被迭代的資料結構。

只要一個資料結構實作了這個協議,就能成為 Iterable,被 for...of、展開運算子、Array.from() 或解構賦值等……語法直接操作。

基於 Iterator Protocol 的概念更是伸展到 Generator Function 相關的知識點,可以創造更多特殊的資料結構。

Iterables

所謂「可迭代」,指的是物件實作了內建的特殊屬性 Symbol.iterator,並回傳一個「迭代器」。 舉例來說,陣列天生就是可迭代的:

const arr = [1, 2, 3];
console.log(typeof arr[Symbol.iterator]); // 'function'

Iterator

Iterator 必須透過 next 來進入下一個迭代,每次呼叫 next() 都會回傳一個物件 { value, done },分別代表目前的值與是否結束:

const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

範例

可以自己定義一個可迭代的物件,例如一個「範圍生成器」:

const range = {
from: 1,
to: 3,
[Symbol.iterator]() {
let current = this.from;
const end = this.to;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
} else {
return { done: true };
}
},
};
},
};
for (const num of range) {
console.log(num); // 1, 2, 3
}

Generator:更簡潔的 Iterator 實作方式

上面的 range 雖然可行,但看起來有點繁瑣。 Generator 函式提供了一種更直覺的方式來建立迭代器。

function* range(from, to) {
for (let i = from; i <= to; i++) {
yield i;
}
}
for (const num of range(1, 3)) {
console.log(num); // 1, 2, 3
}

Generator 是一種特殊的函式它並不會立即執行它內部的程式碼,可以在執行過程中「暫停」與「繼續」,每次執行 yield 都會回傳一個 { value, done } 結構的結果,改寫先前案例:

function* threeStepGenerator() {
yield 1;
yield 2;
yield 3;
console.log('End');
}
const gen = threeStepGenerator();
console.log('Generator 已建立');
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

總結

  • Iterable:擁有 Symbol.iterator 方法的物件。
  • Symbol.iterator:具有 next() 方法、能逐步回傳 { value, done } 的物件。
  • Generator:透過 function* 定義、內部使用 yield 產生值的語法糖,本質上自動幫你建立 Iterator。

實戰上惰性求值、非同步控制、資料流生成、協程模擬、迭代器封裝都可以應用上相關概念。

延伸閱讀