迭代器和生成器

迭代器和生成器将迭代的概念直接带入核心语言,并提供自定义 for...of 循环行为的机制。

¥Iterators and Generators bring the concept of iteration directly into the core language and provide a mechanism for customizing the behavior of for...of loops.

有关详细信息,另请参阅:

¥For details, see also:

迭代器

¥Iterators

在 JavaScript 中,迭代器是一个对象,它定义了一个序列,并可能在其终止时定义一个返回值。

¥In JavaScript an iterator is an object which defines a sequence and potentially a return value upon its termination.

具体来说,迭代器是通过具有 next() 方法来实现 迭代器协议 的任何对象,该方法返回具有两个属性的对象:

¥Specifically, an iterator is any object which implements the Iterator protocol by having a next() method that returns an object with two properties:

value

迭代序列中的下一个值。

done

如果序列中的最后一个值已被消耗,则这是 true。如果 valuedone 一起出现,则它是迭代器的返回值。

一旦创建,迭代器对象就可以通过重复调用 next() 来显式迭代。对迭代器进行迭代被称为消耗迭代器,因为它通常只能执行一次。产生终止值后,对 next() 的其他调用应继续返回 {done: true}

¥Once created, an iterator object can be iterated explicitly by repeatedly calling next(). Iterating over an iterator is said to consume the iterator, because it is generally only possible to do once. After a terminating value has been yielded additional calls to next() should continue to return {done: true}.

JavaScript 中最常见的迭代器是数组迭代器,它按顺序返回关联数组中的每个值。

¥The most common iterator in JavaScript is the Array iterator, which returns each value in the associated array in sequence.

虽然很容易想象所有迭代器都可以表示为数组,但事实并非如此。数组必须完整分配,但迭代器仅在必要时使用。因此,迭代器可以表达无限大小的序列,例如 0Infinity 之间的整数范围。

¥While it is easy to imagine that all iterators could be expressed as arrays, this is not true. Arrays must be allocated in their entirety, but iterators are consumed only as necessary. Because of this, iterators can express sequences of unlimited size, such as the range of integers between 0 and Infinity.

这是一个可以做到这一点的示例。它允许创建一个简单的范围迭代器,该迭代器定义从 start(包含)到 end(不包含)、间隔 step 的整数序列。它的最终返回值是它创建的序列的大小,由变量 iterationCount 跟踪。

¥Here is an example which can do just that. It allows creation of a simple range iterator which defines a sequence of integers from start (inclusive) to end (exclusive) spaced step apart. Its final return value is the size of the sequence it created, tracked by the variable iterationCount.

js
function makeRangeIterator(start = 0, end = Infinity, step = 1) {
  let nextIndex = start;
  let iterationCount = 0;

  const rangeIterator = {
    next() {
      let result;
      if (nextIndex < end) {
        result = { value: nextIndex, done: false };
        nextIndex += step;
        iterationCount++;
        return result;
      }
      return { value: iterationCount, done: true };
    },
  };
  return rangeIterator;
}

使用迭代器看起来像这样:

¥Using the iterator then looks like this:

js
const iter = makeRangeIterator(1, 10, 2);

let result = iter.next();
while (!result.done) {
  console.log(result.value); // 1 3 5 7 9
  result = iter.next();
}

console.log("Iterated over sequence of size:", result.value); // [5 numbers returned, that took interval in between: 0 to 10]

注意:不可能通过反射知道特定对象是否是迭代器。如果你需要这样做,请使用 可迭代对象

¥Note: It is not possible to know reflectively whether a particular object is an iterator. If you need to do this, use Iterables.

生成器功能

¥Generator functions

虽然自定义迭代器是一个有用的工具,但由于需要显式维护其内部状态,因此它们的创建需要仔细编程。生成器函数提供了一个强大的替代方案:它们允许你通过编写执行不连续的单个函数来定义迭代算法。生成器函数是使用 function* 语法编写的。

¥While custom iterators are a useful tool, their creation requires careful programming due to the need to explicitly maintain their internal state. Generator functions provide a powerful alternative: they allow you to define an iterative algorithm by writing a single function whose execution is not continuous. Generator functions are written using the function* syntax.

调用时,生成器函数最初并不执行其代码。相反,它们返回一种特殊类型的迭代器,称为生成器。当通过调用生成器的 next 方法消耗一个值时,生成器函数将执行直到遇到 yield 关键字。

¥When called, generator functions do not initially execute their code. Instead, they return a special type of iterator, called a Generator. When a value is consumed by calling the generator's next method, the Generator function executes until it encounters the yield keyword.

该函数可以根据需要多次调用,并且每次都会返回一个新的生成器。每个生成器只能迭代一次。

¥The function can be called as many times as desired, and returns a new Generator each time. Each Generator may only be iterated once.

我们现在可以调整上面的示例。这段代码的行为是相同的,但实现更容易编写和阅读。

¥We can now adapt the example from above. The behavior of this code is identical, but the implementation is much easier to write and read.

js
function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
  let iterationCount = 0;
  for (let i = start; i < end; i += step) {
    iterationCount++;
    yield i;
  }
  return iterationCount;
}

可迭代对象

¥Iterables

如果一个对象定义了其迭代行为,例如在 for...of 构造中循环哪些值,则该对象是可迭代的。某些内置类型(例如 ArrayMap)具有默认迭代行为,而其他类型(例如 Object)则没有。

¥An object is iterable if it defines its iteration behavior, such as what values are looped over in a for...of construct. Some built-in types, such as Array or Map, have a default iteration behavior, while other types (such as Object) do not.

为了可迭代,对象必须实现 @@iterator 方法。这意味着该对象(或其 原型链 之前的对象之一)必须具有带有 Symbol.iterator 键的属性。

¥In order to be iterable, an object must implement the @@iterator method. This means that the object (or one of the objects up its prototype chain) must have a property with a Symbol.iterator key.

可以对可迭代对象进行多次迭代或仅迭代一次。程序员知道是哪种情况。

¥It may be possible to iterate over an iterable more than once, or only once. It is up to the programmer to know which is the case.

只能迭代一次的迭代器(例如生成器)通常从其 @@iterator 方法返回 this,而可以迭代多次的迭代器必须在每次调用 @@iterator 时返回一个新的迭代器。

¥Iterables which can iterate only once (such as Generators) customarily return this from their @@iterator method, whereas iterables which can be iterated many times must return a new iterator on each invocation of @@iterator.

js
function* makeIterator() {
  yield 1;
  yield 2;
}

const iter = makeIterator();

for (const itItem of iter) {
  console.log(itItem);
}

console.log(iter[Symbol.iterator]() === iter); // true

// This example show us generator(iterator) is iterable object,
// which has the @@iterator method return the `iter` (itself),
// and consequently, the it object can iterate only _once_.

// If we change the @@iterator method of `iter` to a function/generator
// which returns a new iterator/generator object, `iter`
// can iterate many times

iter[Symbol.iterator] = function* () {
  yield 2;
  yield 1;
};

用户定义的迭代

¥User-defined iterables

你可以像这样创建自己的迭代:

¥You can make your own iterables like this:

js
const myIterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  },
};

用户定义的迭代可以像往常一样在 for...of 循环或扩展语法中使用。

¥User-defined iterables can be used in for...of loops or the spread syntax as usual.

js
for (const value of myIterable) {
  console.log(value);
}
// 1
// 2
// 3

[...myIterable]; // [1, 2, 3]

内置可迭代对象

¥Built-in iterables

StringArrayTypedArrayMapSet 都是内置的可迭代对象,因为它们的原型对象都有一个 Symbol.iterator 方法。

¥String, Array, TypedArray, Map and Set are all built-in iterables, because their prototype objects all have a Symbol.iterator method.

需要迭代的语法

¥Syntaxes expecting iterables

一些语句和表达式需要可迭代。例如:for...of 循环、spread syntaxyield*destructuring 语法。

¥Some statements and expressions expect iterables. For example: the for...of loops, spread syntax, yield*, and destructuring syntax.

js
for (const value of ["a", "b", "c"]) {
  console.log(value);
}
// "a"
// "b"
// "c"

[..."abc"];
// ["a", "b", "c"]

function* gen() {
  yield* ["a", "b", "c"];
}

gen().next();
// { value: "a", done: false }

[a, b, c] = new Set(["a", "b", "c"]);
a;
// "a"

高级生成器

¥Advanced generators

生成器按需计算 yield 值,这使它们能够有效地表示计算成本较高的序列(甚至无限序列,如上所示)。

¥Generators compute their yielded values on demand, which allows them to efficiently represent sequences that are expensive to compute (or even infinite sequences, as demonstrated above).

next() 方法还接受一个值,该值可用于修改生成器的内部状态。传递给 next() 的值将由 yield 接收。

¥The next() method also accepts a value, which can be used to modify the internal state of the generator. A value passed to next() will be received by yield .

注意:传递给第一次调用 next() 的值始终被忽略。

¥Note: A value passed to the first invocation of next() is always ignored.

这是使用 next(x) 重新启动序列的斐波那契生成器:

¥Here is the fibonacci generator using next(x) to restart the sequence:

js
function* fibonacci() {
  let current = 0;
  let next = 1;
  while (true) {
    const reset = yield current;
    [current, next] = [next, next + current];
    if (reset) {
      current = 0;
      next = 1;
    }
  }
}

const sequence = fibonacci();
console.log(sequence.next().value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
console.log(sequence.next().value); // 3
console.log(sequence.next().value); // 5
console.log(sequence.next().value); // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2

你可以通过调用生成器的 throw() 方法并传递它应抛出的异常值来强制生成器抛出异常。该异常将从生成器的当前挂起上下文中抛出,就好像当前挂起的 yieldthrow value 语句一样。

¥You can force a generator to throw an exception by calling its throw() method and passing the exception value it should throw. This exception will be thrown from the current suspended context of the generator, as if the yield that is currently suspended were instead a throw value statement.

如果异常没有从生成器内部捕获,它将通过对 throw() 的调用向上传播,并且对 next() 的后续调用将导致 done 属性为 true

¥If the exception is not caught from within the generator, it will propagate up through the call to throw(), and subsequent calls to next() will result in the done property being true.

生成器有一个 return() 方法,它返回给定值并完成生成器本身。

¥Generators have a return() method that returns the given value and finishes the generator itself.