迭代协议

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

¥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.

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

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

[Symbol.iterator]

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

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

¥Whenever an object needs to be iterated (such as at the beginning of a for...of loop), its @@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() 调用并且可以执行任何清理操作。

throw(exception) Optional

接受零个或一个参数并返回符合 IteratorResult 接口的对象的函数,通常 done 等于 true。调用此方法告诉迭代器调用者检测到错误条件,并且 exception 通常是 Error 实例。

注意:不可能反射地知道(即没有实际调用 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[@@iterator]() 方法即可。

¥It is very easy to make an iterator also iterable: just implement an [@@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 @@iterator method (which returns the right iterator), so it's an iterable

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

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

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

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

¥However, when possible, it's better for iterable[Symbol.iterator] to return different iterators that always start from the beginning, like Set.prototype[@@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 对象都实现了 @@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 @@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、数组扩展等(即它有 [@@asyncIterator]() 但没有 [@@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 [@@asyncIterator]() but no [@@iterator]()) will throw a TypeError: x is not iterable.

非格式良好的迭代

¥Non-well-formed iterables

如果可迭代对象的 @@iterator 方法不返回迭代器对象,则它被视为非格式良好的可迭代对象。

¥If an iterable's @@iterator method doesn't return an iterator object, then it's considered a non-well-formed iterable.

使用其中之一可能会导致运行时错误或错误行为:

¥Using one is likely to result in runtime errors or buggy behavior:

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

示例

¥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 `[@@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 }

你可以通过提供我们自己的 @@iterator 来重新定义迭代行为:

¥You can redefine the iteration behavior by supplying our own @@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,
  };
};

请注意重新定义 @@iterator 如何影响使用迭代协议的内置构造的行为:

¥Notice how redefining @@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

也可以看看