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。

实战上惰性求值、异步控制、数据流生成、协程模拟、迭代器封装都可以应用上相关概念。

延伸阅读