迭代协议
迭代协议不是新的内置函数或语法,而是协议。这些协议可以由任何对象通过遵循一些约定来实现。
¥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
构造中循环哪些值。某些内置类型是具有默认迭代行为的 内置可迭代对象,例如 Array
或 Map
,而其他类型(例如 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
)使用迭代器时返回非对象值(例如false
或undefined
),则会抛出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 值。当
done
为true
时可省略。
实际上,这两种属性都不是严格要求的。如果返回没有任一属性的对象,则它实际上相当于 { 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
方法可以接收一个可供方法主体使用的值。任何内置语言功能都不会传递任何值。传递给 generators 的 next
方法的值将成为相应的 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
等于传入的value
,done
等于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
.
// 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:
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.prototype
,Iterator.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
接口的对象,并且属性与同步迭代器的属性具有相同的语义。
语言和迭代协议之间的交互
内置可迭代对象
¥Built-in iterables
String
、Array
、TypedArray
、Map
、Set
和 Segments
(由 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:
Map()
WeakMap()
Set()
WeakSet()
Promise.all()
Promise.allSettled()
Promise.race()
Promise.any()
Array.from()
Object.groupBy()
Map.groupBy()
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:
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"
当内置语法正在迭代迭代器,并且最后结果的 done
是 false
(即迭代器能够产生更多值)但不需要更多值时,将调用 return
方法(如果存在)。例如,如果在 for...of
循环中遇到 break
或 return
,或者所有标识符都已绑定在数组解构中,则可能会发生这种情况。
¥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.
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:
const nonWellFormedIterable = {};
nonWellFormedIterable[Symbol.iterator] = () => 1;
[...nonWellFormedIterable]; // TypeError: [Symbol.iterator]() returned a non-object value
示例
用户定义的迭代
简单迭代器
¥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.
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
无限迭代器
使用生成器定义可迭代对象
¥Defining an iterable with a generator
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.
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:
const someString = "hi";
console.log(typeof someString[Symbol.iterator]); // "function"
String
的 默认迭代器 一一返回字符串的代码点:
¥String
's default iterator returns the string's code points one by one:
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
:
// 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:
console.log([...someString]); // ["bye"]
console.log(`${someString}`); // "hi"
规范
Specification |
---|
ECMAScript Language Specification # sec-iteration |
也可以看看
¥See also