迭代协议

迭代协议不是新的内置函数或语法,而是协议。这些协议可以由任何对象通过遵循一些约定来实现。

¥Iteration protocols aren't new built-ins or syntax, but protocols. These protocols can be implemented by any object by following some conventions.

有两种协议:可迭代协议迭代器协议

¥There are two protocols: The iterable protocol and the iterator protocol.

可迭代协议

¥The iterable protocol

可迭代协议允许 JavaScript 对象定义或自定义其迭代行为,例如在 for...of 构造中循环哪些值。某些内置类型是具有默认迭代行为的 内置可迭代对象,例如 ArrayMap,而其他类型(例如 Object)则不是。

¥The iterable protocol allows JavaScript objects to define or customize their iteration behavior, such as what values are looped over in a for...of construct. Some built-in types are built-in iterables with a default iteration behavior, such as Array or Map, while other types (such as Object) are not.

为了可迭代,对象必须实现 [Symbol.iterator]() 方法,这意味着该对象(或其 原型链 之上的对象之一)必须具有带有 [Symbol.iterator] 键的属性,该属性可通过常量 Symbol.iterator 获得:

¥In order to be iterable, an object must implement the [Symbol.iterator]() method, meaning that the object (or one of the objects up its prototype chain) must have a property with a [Symbol.iterator] key which is available via constant Symbol.iterator:

[Symbol.iterator]()

返回一个对象的零参数函数,符合 迭代器协议

每当一个对象需要迭代时(例如在 for...of 循环开始时),就会不带参数地调用其 [Symbol.iterator]() 方法,并使用返回的迭代器来获取要迭代的值。

¥Whenever an object needs to be iterated (such as at the beginning of a for...of loop), its [Symbol.iterator]() method is called with no arguments, and the returned iterator is used to obtain the values to be iterated.

请注意,当调用此零参数函数时,它是作为可迭代对象上的方法调用的。因此,在函数内部,可以使用 this 关键字来访问可迭代对象的属性,以决定在迭代期间提供什么。

¥Note that when this zero-argument function is called, it is invoked as a method on the iterable object. Therefore inside of the function, the this keyword can be used to access the properties of the iterable object, to decide what to provide during the iteration.

该函数可以是普通函数,也可以是生成器函数,以便在调用时返回迭代器对象。在此生成器函数内部,每个条目都可以使用 yield 提供。

¥This function can be an ordinary function, or it can be a generator function, so that when invoked, an iterator object is returned. Inside of this generator function, each entry can be provided by using yield.

迭代器协议

¥The iterator protocol

迭代器协议定义了一种生成值序列(有限或无限)的标准方法,并且在生成所有值时可能会返回一个返回值。

¥The iterator protocol defines a standard way to produce a sequence of values (either finite or infinite), and potentially a return value when all values have been generated.

当一个对象实现具有以下语义的 next() 方法时,该对象就是一个迭代器:

¥An object is an iterator when it implements a next() method with the following semantics:

next()

一个接受零个或一个参数并返回符合 IteratorResult 接口的对象的函数(见下文)。如果当内置语言功能(例如 for...of)使用迭代器时返回非对象值(例如 falseundefined),则会抛出 TypeError ("iterator.next() returned a non-object value")。

所有迭代器协议方法(next()return()throw())都应返回实现 IteratorResult 接口的对象。它必须具有以下属性:

¥All iterator protocol methods (next(), return(), and throw()) are expected to return an object implementing the IteratorResult interface. It must have the following properties:

done Optional

如果迭代器能够生成序列中的下一个值,则为 false 的布尔值。(这相当于完全不指定 done 属性。)

如果迭代器已完成其序列,则值为 true。在这种情况下,value 可选地指定迭代器的返回值。

value Optional

迭代器返回的任何 JavaScript 值。当 donetrue 时可省略。

实际上,这两种属性都不是严格要求的。如果返回没有任一属性的对象,则它实际上相当于 { done: false, value: undefined }

¥In practice, neither property is strictly required; if an object without either property is returned, it's effectively equivalent to { done: false, value: undefined }.

如果迭代器返回 done: true 的结果,则对 next() 的任何后续调用也将返回 done: true,尽管这在语言级别上没有强制执行。

¥If an iterator returns a result with done: true, any subsequent calls to next() are expected to return done: true as well, although this is not enforced on the language level.

next 方法可以接收一个可供方法主体使用的值。任何内置语言功能都不会传递任何值。传递给 generatorsnext 方法的值将成为相应的 yield 表达式的值。

¥The next method can receive a value which will be made available to the method body. No built-in language feature will pass any value. The value passed to the next method of generators will become the value of the corresponding yield expression.

或者,迭代器还可以实现 return(value)throw(exception) 方法,这些方法在被调用时告诉迭代器调用者已完成迭代,并且可以执行任何必要的清理(例如关闭数据库连接)。

¥Optionally, the iterator can also implement the return(value) and throw(exception) methods, which, when called, tells the iterator that the caller is done with iterating it and can perform any necessary cleanup (such as closing database connection).

return(value) Optional

接受零个或一个参数并返回符合 IteratorResult 接口的对象的函数,通常 value 等于传入的 valuedone 等于 true。调用此方法告诉迭代器调用者不打算再进行任何 next() 调用并且可以执行任何清理操作。当内置语言功能调用 return() 进行清理时,value 始终是 undefined

throw(exception) Optional

接受零个或一个参数并返回符合 IteratorResult 接口的对象的函数,通常 done 等于 true。调用此方法告诉迭代器调用者检测到错误条件,并且 exception 通常是 Error 实例。没有内置语言功能调用 throw() 进行清理 - 它是生成器的特殊功能,用于 return/throw 的对称性。

注意:不可能反射地知道(即没有实际调用 next() 并验证返回的结果)特定对象是否实现迭代器协议。

¥Note: It is not possible to know reflectively (i.e. without actually calling next() and validating the returned result) whether a particular object implements the iterator protocol.

使迭代器也可迭代非常容易:只需实现一个返回 this[Symbol.iterator]() 方法即可。

¥It is very easy to make an iterator also iterable: just implement an [Symbol.iterator]() method that returns this.

js
// Satisfies both the Iterator Protocol and Iterable
const myIterator = {
  next() {
    // ...
  },
  [Symbol.iterator]() {
    return this;
  },
};

这样的对象称为可迭代迭代器。这样做允许迭代器被期望可迭代的各种语法使用 - 因此,在不实现可迭代的情况下实现迭代器协议很少有用。(事实上,几乎所有语法和 API 都期望迭代器,而不是迭代器。)生成器对象 是一个例子:

¥Such object is called an iterable iterator. Doing so allows an iterator to be consumed by the various syntaxes expecting iterables — therefore, it is seldom useful to implement the Iterator Protocol without also implementing Iterable. (In fact, almost all syntaxes and APIs expect iterables, not iterators.) The generator object is an example:

js
const aGeneratorObject = (function* () {
  yield 1;
  yield 2;
  yield 3;
})();

console.log(typeof aGeneratorObject.next);
// "function" — it has a next method (which returns the right result), so it's an iterator

console.log(typeof aGeneratorObject[Symbol.iterator]);
// "function" — it has an [Symbol.iterator] method (which returns the right iterator), so it's an iterable

console.log(aGeneratorObject[Symbol.iterator]() === aGeneratorObject);
// true — its [Symbol.iterator] method returns itself (an iterator), so it's an iterable iterator

所有内置迭代器都继承自 Iterator.prototypeIterator.prototype[Symbol.iterator]() 方法实现为返回 this,因此内置迭代器也是可迭代的。

¥All built-in iterators inherit from Iterator.prototype, which implements the [Symbol.iterator]() method as returning this, so that built-in iterators are also iterable.

但是,如果可能的话,iterable[Symbol.iterator]() 最好返回始终从头开始的不同迭代器,就像 Set.prototype[Symbol.iterator]() 那样。

¥However, when possible, it's better for iterable[Symbol.iterator]() to return different iterators that always start from the beginning, like Set.prototype[Symbol.iterator]() does.

异步迭代器和异步可迭代协议

¥The async iterator and async iterable protocols

还有另一对用于异步迭代的协议,称为异步迭代器和异步可迭代协议。与可迭代和迭代器协议相比,它们具有非常相似的接口,只是调用迭代器方法的每个返回值都封装在一个 Promise 中。

¥There are another pair of protocols used for async iteration, named async iterator and async iterable protocols. They have very similar interfaces compared to the iterable and iterator protocols, except that each return value from the calls to the iterator methods is wrapped in a promise.

当对象实现以下方法时,它就实现了异步可迭代协议:

¥An object implements the async iterable protocol when it implements the following methods:

[Symbol.asyncIterator]()

返回一个对象的零参数函数,符合异步迭代器协议。

当对象实现以下方法时,它就实现了异步迭代器协议:

¥An object implements the async iterator protocol when it implements the following methods:

next()

一个接受零个或一个参数并返回一个承诺的函数。Promise 会满足符合 IteratorResult 接口的对象,并且属性与同步迭代器的属性具有相同的语义。

return(value) Optional

一个接受零个或一个参数并返回一个承诺的函数。Promise 会满足符合 IteratorResult 接口的对象,并且属性与同步迭代器的属性具有相同的语义。

throw(exception) Optional

一个接受零个或一个参数并返回一个承诺的函数。Promise 会满足符合 IteratorResult 接口的对象,并且属性与同步迭代器的属性具有相同的语义。

语言和迭代协议之间的交互

¥Interactions between the language and iteration protocols

该语言指定了生成或使用可迭代对象和迭代器的 API。

¥The language specifies APIs that either produce or consume iterables and iterators.

内置可迭代对象

¥Built-in iterables

StringArrayTypedArrayMapSetSegments(由 Intl.Segmenter.prototype.segment() 返回)都是内置可迭代对象,因为它们的每个 prototype 对象都实现了 [Symbol.iterator]() 方法。此外,arguments 对象和一些 DOM 集合类型(例如 NodeList)也是可迭代的。核心 JavaScript 语言中没有异步可迭代的对象。某些 Web API(例如 ReadableStream)默认设置了 Symbol.asyncIterator 方法。

¥String, Array, TypedArray, Map, Set, and Segments (returned by Intl.Segmenter.prototype.segment()) are all built-in iterables, because each of their prototype objects implements an [Symbol.iterator]() method. In addition, the arguments object and some DOM collection types such as NodeList are also iterables. There is no object in the core JavaScript language that is async iterable. Some web APIs, such as ReadableStream, have the Symbol.asyncIterator method set by default.

生成器功能 返回 生成器对象,它们是可迭代的迭代器。异步生成器函数 返回 异步生成器对象,它们是异步可迭代迭代器。

¥Generator functions return generator objects, which are iterable iterators. Async generator functions return async generator objects, which are async iterable iterators.

从内置迭代器返回的迭代器实际上都继承自一个公共类 Iterator,该类实现了前面提到的 [Symbol.iterator]() { return this; } 方法,使它们都是可迭代的迭代器。除了迭代器协议所需的 next() 方法之外,Iterator 类还提供了额外的 辅助方法。你可以通过在图形控制台中记录迭代器的原型链来检查它。

¥The iterators returned from built-in iterables actually all inherit from a common class Iterator, which implements the aforementioned [Symbol.iterator]() { return this; } method, making them all iterable iterators. The Iterator class also provides additional helper methods in addition to the next() method required by the iterator protocol. You can inspect an iterator's prototype chain by logging it in a graphical console.

console.log([][Symbol.iterator]());

Array Iterator {}
  [[Prototype]]: Array Iterator     ==> This is the prototype shared by all array iterators
    next: ƒ next()
    Symbol(Symbol.toStringTag): "Array Iterator"
    [[Prototype]]: Object           ==> This is the prototype shared by all built-in iterators
      Symbol(Symbol.iterator): ƒ [Symbol.iterator]()
      [[Prototype]]: Object         ==> This is Object.prototype

接受可迭代的内置 API

¥Built-in APIs accepting iterables

有许多 API 接受可迭代对象。一些例子包括:

¥There are many APIs that accept iterables. Some examples include:

js
const myObj = {};

new WeakSet(
  (function* () {
    yield {};
    yield myObj;
    yield {};
  })(),
).has(myObj); // true

需要迭代的语法

¥Syntaxes expecting iterables

一些语句和表达式需要迭代,例如 for...of 循环、数组和参数传播yield*数组解构

¥Some statements and expressions expect iterables, for example the for...of loops, array and parameter spreading, yield*, and array destructuring:

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

console.log([..."abc"]); // ["a", "b", "c"]

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

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

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

当内置语法正在迭代迭代器,并且最后结果的 donefalse(即迭代器能够产生更多值)但不需要更多值时,将调用 return 方法(如果存在)。例如,如果在 for...of 循环中遇到 breakreturn,或者所有标识符都已绑定在数组解构中,则可能会发生这种情况。

¥When built-in syntaxes are iterating an iterator, and the last result's done is false (i.e. the iterator is able to produce more values) but no more values are needed, the return method will get called if present. This can happen, for example, if a break or return is encountered in a for...of loop, or if all identifiers are already bound in an array destructuring.

js
const obj = {
  [Symbol.iterator]() {
    let i = 0;
    return {
      next() {
        i++;
        console.log("Returning", i);
        if (i === 3) return { done: true, value: i };
        return { done: false, value: i };
      },
      return() {
        console.log("Closing");
        return { done: true };
      },
    };
  },
};

const [a] = obj;
// Returning 1
// Closing

const [b, c, d] = obj;
// Returning 1
// Returning 2
// Returning 3
// Already reached the end (the last call returned `done: true`),
// so `return` is not called

for (const b of obj) {
  break;
}
// Returning 1
// Closing

异步生成器函数 中的 for await...of 循环和 yield*(但不是 同步生成器功能)是与异步迭代交互的唯一方法。在不是同步迭代的异步迭代上使用 for...of、数组扩展等(即它有 [Symbol.asyncIterator]() 但没有 [Symbol.iterator]())将抛出 TypeError:x 是不可迭代的。

¥The for await...of loop and yield* in async generator functions (but not sync generator functions) are the only ways to interact with async iterables. Using for...of, array spreading, etc. on an async iterable that's not also a sync iterable (i.e. it has [Symbol.asyncIterator]() but no [Symbol.iterator]()) will throw a TypeError: x is not iterable.

错误处理

¥Error handling

因为迭代涉及在迭代器和消费者之间来回转移控制权,所以错误处理以两种方式发生:消费者如何处理迭代器抛出的错误,以及迭代器如何处理消费者抛出的错误。当你使用内置迭代方式之一时,语言也可能会抛出错误,因为可迭代对象破坏了某些不变量。我们将描述内置语法如何生成和处理错误,如果你手动逐步执行迭代器,可以将其用作你自己的代码的指南。

¥Because iteration involves transferring control back and forth between the iterator and the consumer, error handling happens in both ways: how the consumer handles errors thrown by the iterator, and how the iterator handles errors thrown by the consumer. When you are using one of the built-in ways of iteration, the language may also throw errors because the iterable breaks certain invariants. We will describe how built-in syntaxes generate and handle errors, which can be used as a guideline for your own code if you are manually stepping the iterator.

非格式良好的迭代

¥Non-well-formed iterables

从可迭代对象获取迭代器时可能会发生错误。这里强制执行的语言不变量是可迭代对象必须生成一个有效的迭代器:

¥Errors may happen when acquiring the iterator from the iterable. The language invariant enforced here is that the iterable must produce a valid iterator:

  • 它有一个可调用的 [Symbol.iterator]() 方法。
  • [Symbol.iterator]() 方法返回一个对象。
  • [Symbol.iterator]() 返回的对象具有可调用的 next() 方法。

当使用内置语法对格式不正确的可迭代对象启动迭代时,会抛出 TypeError。

¥When using built-in syntax to initiate iteration on a non-well-formed iterable, a TypeError is thrown.

js
const nonWellFormedIterable = { [Symbol.iterator]: 1 };
[...nonWellFormedIterable]; // TypeError: nonWellFormedIterable is not iterable
nonWellFormedIterable[Symbol.iterator] = () => 1;
[...nonWellFormedIterable]; // TypeError: [Symbol.iterator]() returned a non-object value
nonWellFormedIterable[Symbol.iterator] = () => ({});
[...nonWellFormedIterable]; // TypeError: nonWellFormedIterable[Symbol.iterator]().next is not a function

对于异步可迭代对象,如果其 [Symbol.asyncIterator]() 属性具有值 undefinednull,则 JavaScript 将回退为使用 [Symbol.iterator] 属性(并通过 forwarding 方法将生成的迭代器封装到异步迭代器中)。否则,[Symbol.asyncIterator] 属性也必须符合上述不变量。

¥For async iterables, if its [Symbol.asyncIterator]() property has value undefined or null, JavaScript falls back to using the [Symbol.iterator] property instead (and wraps the resulting iterator into an async iterator by forwarding the methods). Otherwise, the [Symbol.asyncIterator] property must conform to the above invariants too.

可以通过在尝试迭代之前先验证可迭代对象来防止此类错误。但是,这种情况相当罕见,因为通常你知道要迭代的对象的类型。如果你从其他代码接收此可迭代对象,则应该让错误传播给调用者,以便他们知道提供了无效的输入。

¥This type of errors can be prevented by first validating the iterable before attempting to iterate it. However, it's fairly rare because usually you know the type of the object you are iterating over. If you are receiving this iterable from some other code, you should just let the error propagate to the caller so they know an invalid input was provided.

迭代过程中的错误

¥Errors during iteration

大多数错误发生在迭代器(调用 next())时。这里强制执行的语言不变量是 next() 方法必须返回一个对象(对于异步迭代器,等待后返回一个对象)。否则,将引发 TypeError。

¥Most errors happen when stepping the iterator (calling next()). The language invariant enforced here is that the next() method must return an object (for async iterators, an object after awaiting). Otherwise, a TypeError is thrown.

如果不变量被破坏或 next() 方法抛出错误(对于异步迭代器,它也可能返回被拒绝的承诺),则错误将传递给调用者。对于内置语法,正在进行的迭代将中止,而无需重试或清理(假设如果 next() 方法引发错误,则它已经清理完毕)。如果你手动调用 next(),你可能会捕获错误并重试调用 next(),但通常你应该假设迭代器已关闭。

¥If the invariant is broken or the next() method throws an error (for async iterators, it may also return a rejected promise), the error is progated to the caller. For built-in syntaxes, the iteration in progress is aborted without retrying or cleanup (with the assumption that if the next() method threw the error, then it has cleaned up already). If you are manually calling next(), you may catch the error and retry calling next(), but in general you should assume the iterator is already closed.

如果调用者决定出于上一段中的错误以外的任何原因退出迭代,例如当它在自己的代码中进入错误状态时(例如,在处理迭代器生成的无效值时),它应该在迭代器上调用 return() 方法(如果存在)。这允许迭代器执行任何清理。return() 方法仅在提前退出时调用 - 如果 next() 返回 done: true,则不会调用 return() 方法,假设迭代器已经清理。

¥If the caller decides to exit iteration for any reason other than the errors in the previous paragraph, such as when it enters an error state in its own code (for example, while handling an invalid value produced by the iterator), it should call the return() method on the iterator, if one exists. This allows the iterator to perform any cleanup. The return() method is only called for premature exits—if next() returns done: true, the return() method is not called, with the assumption that the iterator has already cleaned up.

return() 方法也可能无效!语言还强制要求 return() 方法必须返回一个对象,否则会抛出 TypeError。如果 return() 方法抛出错误,则错误将传播给调用者。但是,如果由于调用者在其自己的代码中遇到错误而调用 return() 方法,则此错误将覆盖 return() 方法抛出的错误。

¥The return() method might be invalid too! The language also enforces that the return() method must return an object and throws a TypeError otherwise. If the return() method throws an error, the error is propagated to the caller. However, if the return() method is called because the caller encountered an error in its own code, then this error overrides the error thrown by the return() method.

通常,调用者会像这样实现错误处理:

¥Usually, the caller implements error handling like this:

js
try {
  for (const value of iterable) {
    // ...
  }
} catch (e) {
  // Handle the error
}

catch 将能够捕获当 iterable 不是有效可迭代对象时、当 next() 抛出错误时、当 return() 抛出错误时(如果 for 循环提前退出)以及当 for 循环体抛出错误时抛出的错误。

¥The catch will be able to catch errors thrown when iterable is not a valid iterable, when next() throws an error, when return() throws an error (if the for loop exits early), and when the for loop body throws an error.

大多数迭代器都是用生成器函数实现的,因此我们将演示生成器函数通常如何处理错误:

¥Most iterators are implemented with generator functions, so we will demonstrate how generator functions typically handle errors:

js
function* gen() {
  try {
    yield doSomething();
    yield doSomethingElse();
  } finally {
    cleanup();
  }
}

这里缺少 catch 会导致 doSomething()doSomethingElse() 抛出的错误传播到 gen 的调用者。如果这些错误在生成器函数中被捕获(这同样可取),则生成器函数可以决定继续产生值或提前退出。但是,finally 块对于保持开放资源的生成器是必需的。finally 块保证运行,无论是在调用最后一个 next() 时还是在调用 return() 时。

¥The lack of catch here causes errors thrown by doSomething() or doSomethingElse() to propagate to the caller of gen. If these errors are caught within the generator function (which is equally advisable), the generator function can decide to continue yielding values or to exit early. However, the finally block is necessary for generators that keep open resources. The finally block is guaranteed to run, either when the last next() is called or when return() is called.

转发错误

¥Forwarding errors

某些内置语法将迭代器封装到另一个迭代器中。它们包括由 Iterator.from()迭代器助手map()filter()take()drop()flatMap())、yield * 生成的迭代器,以及在同步迭代器上使用异步迭代(for await...ofArray.fromAsync)时的隐藏封装器。然后,封装的迭代器负责在内部迭代器和调用者之间转发错误。

¥Some built-in syntaxes wrap an iterator into another iterator. They include the iterator produced by Iterator.from(), iterator helpers (map(), filter(), take(), drop(), and flatMap()), yield *, and a hidden wrapper when you use async iteration (for await...of, Array.fromAsync) on sync iterators. The wrapped iterator is then responsible for forwarding errors between the inner iterator and the caller.

  • 所有封装器迭代器都直接转发内部迭代器的 next() 方法,包括其返回值和抛出的错误。
  • 封装器迭代器通常直接转发内部迭代器的 return() 方法。如果内部迭代器上不存在 return() 方法,则返回 { done: true, value: undefined }。对于迭代器助手:如果迭代器助手的 next() 方法尚未调用,则在尝试在内部迭代器上调用 return() 后,当前迭代器始终返回 { done: true, value: undefined }。这与执行尚未进入 yield * 表达式的生成器函数一致。
  • yield * 是唯一转发内部迭代器的 throw() 方法的内置语法。有关 yield * 如何转发 return()throw() 方法的信息,请参阅其自己的参考。

示例

¥Examples

用户定义的迭代

¥User-defined iterables

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

¥You can make your own iterables like this:

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

console.log([...myIterable]); // [1, 2, 3]

简单迭代器

¥Simple iterator

迭代器本质上是有状态的。如果你不将其定义为 生成器函数(如上面的示例所示),你可能希望将状态封装在闭包中。

¥Iterators are stateful by nature. If you don't define it as a generator function (as the example above shows), you would likely want to encapsulate the state in a closure.

js
function makeIterator(array) {
  let nextIndex = 0;
  return {
    next() {
      return nextIndex < array.length
        ? {
            value: array[nextIndex++],
            done: false,
          }
        : {
            done: true,
          };
    },
  };
}

const it = makeIterator(["yo", "ya"]);

console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done); // true

无限迭代器

¥Infinite iterator

js
function idMaker() {
  let index = 0;
  return {
    next() {
      return {
        value: index++,
        done: false,
      };
    },
  };
}

const it = idMaker();

console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2
// ...

使用生成器定义可迭代对象

¥Defining an iterable with a generator

js
function* makeSimpleGenerator(array) {
  let nextIndex = 0;
  while (nextIndex < array.length) {
    yield array[nextIndex++];
  }
}

const gen = makeSimpleGenerator(["yo", "ya"]);

console.log(gen.next().value); // 'yo'
console.log(gen.next().value); // 'ya'
console.log(gen.next().done); // true

function* idMaker() {
  let index = 0;
  while (true) {
    yield index++;
  }
}

const it = idMaker();

console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2
// ...

用类定义可迭代对象

¥Defining an iterable with a class

状态封装也可以用 私有属性 来完成。

¥State encapsulation can be done with private properties as well.

js
class SimpleClass {
  #data;

  constructor(data) {
    this.#data = data;
  }

  [Symbol.iterator]() {
    // Use a new index for each iterator. This makes multiple
    // iterations over the iterable safe for non-trivial cases,
    // such as use of break or nested looping over the same iterable.
    let index = 0;

    return {
      // Note: using an arrow function allows `this` to point to the
      // one of `[Symbol.iterator]()` instead of `next()`
      next: () => {
        if (index < this.#data.length) {
          return { value: this.#data[index++], done: false };
        } else {
          return { done: true };
        }
      },
    };
  }
}

const simple = new SimpleClass([1, 2, 3, 4, 5]);

for (const val of simple) {
  console.log(val); // 1 2 3 4 5
}

重写内置的可迭代对象

¥Overriding built-in iterables

例如,String 是一个内置的可迭代对象:

¥For example, a String is a built-in iterable object:

js
const someString = "hi";
console.log(typeof someString[Symbol.iterator]); // "function"

String默认迭代器 一一返回字符串的代码点:

¥String's default iterator returns the string's code points one by one:

js
const iterator = someString[Symbol.iterator]();
console.log(`${iterator}`); // "[object String Iterator]"

console.log(iterator.next()); // { value: "h", done: false }
console.log(iterator.next()); // { value: "i", done: false }
console.log(iterator.next()); // { value: undefined, done: true }

你可以通过提供我们自己的 [Symbol.iterator]() 来重新定义迭代行为:

¥You can redefine the iteration behavior by supplying our own [Symbol.iterator]():

js
// need to construct a String object explicitly to avoid auto-boxing
const someString = new String("hi");

someString[Symbol.iterator] = function () {
  return {
    // this is the iterator object, returning a single element (the string "bye")
    next() {
      return this._first
        ? { value: "bye", done: (this._first = false) }
        : { done: true };
    },
    _first: true,
  };
};

请注意重新定义 [Symbol.iterator]() 如何影响使用迭代协议的内置构造的行为:

¥Notice how redefining [Symbol.iterator]() affects the behavior of built-in constructs that use the iteration protocol:

js
console.log([...someString]); // ["bye"]
console.log(`${someString}`); // "hi"

规范

Specification
ECMAScript Language Specification
# sec-iteration

¥Specifications

也可以看看