Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zh-cn: update iterators_and_generators #16822

Merged
merged 4 commits into from
Nov 6, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 79 additions & 41 deletions files/zh-cn/web/javascript/guide/iterators_and_generators/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ title: 迭代器和生成器
slug: Web/JavaScript/Guide/Iterators_and_generators
---

{{jsSidebar("JavaScript Guide")}} {{PreviousNext("Web/JavaScript/Guide/Using_promises", "Web/JavaScript/Guide/Meta_programming")}}
{{jsSidebar("JavaScript Guide")}} {{PreviousNext("Web/JavaScript/Guide/Typed_arrays", "Web/JavaScript/Guide/Meta_programming")}}

处理集合中的每个项是很常见的操作。JavaScript 提供了许多迭代集合的方法,从简单的 {{jsxref("Statements/for","for")}} 循环到 {{jsxref("Global_Objects/Array/map","map()")}} 和 {{jsxref("Global_Objects/Array/filter","filter()")}}。迭代器和生成器将迭代的概念直接带入核心语言,并提供了一种机制来自定义 {{jsxref("Statements/for...of","for...of")}} 循环的行为。
迭代器和生成器将迭代的概念直接带入核心语言,并提供了一种机制来自定义 {{jsxref("Statements/for...of","for...of")}} 循环的行为。

若想了解更多详情,请参考:

Expand All @@ -16,21 +16,30 @@ slug: Web/JavaScript/Guide/Iterators_and_generators

## 迭代器

在 JavaScript 中,**迭代器**是一个对象,它定义一个序列,并在终止时可能返回一个返回值。更具体地说,迭代器是通过使用 `next()` 方法实现 [Iterator protocol](/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterator_protocol) 的任何一个对象,该方法返回具有两个属性的对象: `value`,这是序列中的 next 值;和 `done` ,如果已经迭代到序列中的最后一个值,则它为 `true` 。如果 `value` 和 `done` 一起存在,则它是迭代器的返回值
在 JavaScript 中,**迭代器**是一个对象,它定义一个序列,并在终止时可能附带一个返回值

一旦创建,迭代器对象可以通过重复调用 next()显式地迭代。迭代一个迭代器被称为消耗了这个迭代器,因为它通常只能执行一次。在产生终止值之后,对 next()的额外调用应该继续返回{done:true}。
更具体地说,迭代器是通过使用 `next()` 方法实现了[迭代器协议](/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#迭代器协议)的任何一个对象,该方法返回具有两个属性的对象:

Javascript 中最常见的迭代器是 Array 迭代器,它只是按顺序返回关联数组中的每个值。虽然很容易想象所有迭代器都可以表示为数组,但事实并非如此。数组必须完整分配,但迭代器仅在必要时使用,因此可以表示无限大小的序列,例如 0 和无穷大之间的整数范围。
- `value`
- : 迭代序列的下一个值。
- `done`
- : 如果已经迭代到序列中的最后一个值,则它为 `true`。如果 `value` 和 `done` 一起出现,则它就是迭代器的返回值。

这是一个可以做到这一点的例子。它允许创建一个简单的范围迭代器,它定义了从开始(包括)到结束(独占)间隔步长的整数序列。它的最终返回值是它创建的序列的大小,由变量 iterationCount 跟踪。
一旦创建,迭代器对象可以通过重复调用 `next()` 显式地迭代。迭代一个迭代器被称为消耗了这个迭代器,因为它通常只能执行一次:在产生终值后,对 `next()` 的额外调用应该继续返回 `{done:true}`。

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

虽然很容易想象所有迭代器都可以表示为数组,但事实并非如此。数组必须完整分配,而迭代器则是按需分配。因此,迭代器可以表示无限大小的序列,例如 0 和 {{jsxref("Infinity")}} 之间的整数范围。

下面的例子展示了具体做法。它允许你创建一个简单的范围迭代器,以定义一个从 `start`(闭)到 `end`(开),以 `step` 为步长的整数序列。它的最终返回值是它创建的序列的大小,由变量 `iterationCount` 跟踪。

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

const rangeIterator = {
next: function () {
next() {
let result;
if (nextIndex < end) {
result = { value: nextIndex, done: false };
Expand All @@ -56,41 +65,68 @@ while (!result.done) {
result = it.next();
}

console.log("Iterated over sequence of size: ", result.value); // 5
console.log(`已迭代序列的大小:${result.value}`); // 5
```

> **备注:** 反射性地知道特定对象是否是迭代器是不可能的。如果你需要这样做,请使用 [Iterables](#Iterables).
> **备注:** [反射性](https://zh.wikipedia.org/wiki/反射式编程)地知道特定对象是否是迭代器是不可能的。如果你需要这样做,请使用[可迭代对象](#可迭代对象)。

## 生成器函数

虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数,同时它可以自动维护自己的状态。生成器函数使用 {{jsxref("Statements/function*","function*")}}语法编写。最初调用时,生成器函数不执行任何代码,而是返回一种称为 Generator 的迭代器。通过调用生成器的下一个方法消耗值时,Generator 函数将执行,直到遇到 yield 关键字
虽然自定义迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此创建时要格外谨慎。**生成器函数**(Generator 函数)提供了一个强大的替代选择:它允许你定义一个非连续执行的函数作为迭代算法。生成器函数使用 {{jsxref("Statements/function*","function*")}} 语法编写

可以根据需要多次调用该函数,并且每次都返回一个新的 Generator,但每个 Generator 只能迭代一次
最初调用时,生成器函数不执行任何代码,而是返回一种称为**生成器**的特殊迭代器。通过调用 `next()` 方法消耗该生成器时,生成器函数将执行,直至遇到 `yield` 关键字

我们现在可以调整上面的例子了。此代码的行为是相同的,但实现更容易编写和阅读。
可以根据需要多次调用该函数,并且每次都返回一个新的生成器,但每个生成器只能迭代一次。

我们现在可以调整上面的例子了。此代码的行为并没有改变,但更容易编写和阅读。

```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;
}
var a = makeRangeIterator(1, 10, 2);
a.next(); // {value: 1, done: false}
a.next(); // {value: 3, done: false}
a.next(); // {value: 5, done: false}
a.next(); // {value: 7, done: false}
a.next(); // {value: 9, done: false}
a.next(); // {value: undefined, done: true}
```

## 可迭代对象

若一个对象拥有迭代行为,比如在 {{jsxref("Statements/for...of", "for...of")}} 中会循环哪些值,那么那个对象便是一个可迭代对象。一些内置类型,如 {{jsxref("Array")}} 或 {{jsxref("Map")}} 拥有默认的迭代行为,而其他类型(比如{{jsxref("Object")}})则没有。
若一个对象拥有迭代行为,比如在 {{jsxref("Statements/for...of", "for...of")}} 中会循环一些值,那么那个对象便是一个可迭代对象。一些内置类型,如 {{jsxref("Array")}} 或 {{jsxref("Map")}} 拥有默认的迭代行为,而其他类型(比如 {{jsxref("Object")}})则没有。

为了实现**可迭代**,一个对象必须实现 **@@iterator** 方法,这意味着这个对象(或其[原型链](/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)中的任意一个对象)必须具有一个键值为 {{jsxref("Symbol.iterator")}} 的属性。

为了实现**可迭代**,一个对象必须实现 **@@iterator** 方法,这意味着这个对象(或其[原型链](/zh-CN/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain)中的任意一个对象)必须具有一个带 {{jsxref("Symbol.iterator")}} 键(key)的属性
程序员应知道一个可迭代对象可以多次迭代,还是只能迭代一次

可以多次迭代一个迭代器,或者只迭代一次。程序员应该知道是哪种情况。只能迭代一次的 Iterables(例如 Generators)通常从它们的**@@iterator**方法中返回它本身,其中那些可以多次迭代的方法必须在每次调用**@@iterator**时返回一个新的迭代器。
只能迭代一次的可迭代对象(例如 Generator)通常从它们的 **@@iterator** 方法中返回 `this`,而那些可以多次迭代的方法必须在每次调用 **@@iterator** 时返回一个新的迭代器。

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

const it = makeIterator();

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

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

// 这个例子向我们展示了生成器(迭代器)是可迭代对象,
// 它有一个 @@iterator 方法返回 it(它自己),
// 因此,it 对象只能迭代*一次*。

// 如果我们将它的 @@iterator 方法改为一个返回新的迭代器/生成器对象的函数/生成器,
// 它(it)就可以迭代多次了。

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

### 自定义的可迭代对象

Expand All @@ -104,16 +140,18 @@ var myIterable = {
yield 3;
},
};
```

自定义的可迭代对象可用 `for...of` 循环或者展开语法进行迭代。

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

// 或者

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

Expand All @@ -123,7 +161,7 @@ for (let value of myIterable) {

### 用于可迭代对象的语法

一些语句和表达式专用于可迭代对象,例如 {{jsxref("Statements/for...of","for-of")}} 循环{{jsxref("Operators/Spread_operator","展开语法")}}{{jsxref("Operators/yield*", "yield*")}} 和 {{jsxref("Operators/Destructuring_assignment", "解构赋值")}}。
一些语句和表达式专用于可迭代对象,例如 {{jsxref("Statements/for...of", "for...of")}} 循环{{jsxref("Operators/Spread_syntax", "展开语法", "", 1)}}{{jsxref("Operators/yield*", "yield*")}} 和{{jsxref("Operators/Destructuring_assignment", "解构", "", 1)}}语法

```js
for (let value of ["a", "b", "c"]) {
Expand All @@ -147,29 +185,29 @@ a; // "a"

## 高级生成器

生成器会按需计算它们的产生值,这使得它们能够有效的表示一个计算成本很高的序列,甚至是如上所示的一个无限序列。
生成器会*按需*计算它们 `yield` 的值,这使得它们能够高效地表示一个计算成本很高的序列,甚至是前文所示的一个无限序列。

{{jsxref("Global_Objects/Generator/next", "next()")}} 方法也接受一个参数用于修改生成器内部状态。传递给 `next()` 的参数值会被 `yield` 接收。

The {{jsxref("Global_Objects/Generator/next","next()")}} 方法也接受一个参数用于修改生成器内部状态。传递给 `next()` 的参数值会被 yield 接收。要注意的是,传给第一个 `next()` 的值会被忽略。
> **备注:** 传给*第一个* `next()` 的值会被忽略。

下面的是斐波那契数列生成器,它使用了 `next(x)` 来重新启动序列
下面的是斐波那契数列生成器,它使用了 `next(x)` 来重启序列

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

var sequence = fibonacci();
const sequence = fibonacci();
console.log(sequence.next().value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
Expand All @@ -183,10 +221,10 @@ console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
```

你可以通过调用其 {{jsxref("Global_Objects/Generator/throw","throw()")}} 方法强制生成器抛出异常,并传递应该抛出的异常值。这个异常将从当前挂起的生成器的上下文中抛出,就好像当前挂起的 `yield` 是一个 `throw value` 语句。
你可以通过调用其 {{jsxref("Global_Objects/Generator/throw", "throw()")}} 方法强制生成器抛出异常,并传递应该抛出的异常值。这个异常将从当前挂起的生成器的上下文中抛出,就好像当前挂起的 `yield` 是一个 `throw value` 语句。

如果在抛出的异常处理期间没有遇到 `yield`,则异常将通过调用 `throw()` 向上传播,对 `next()` 的后续调用将导致 `done` 属性为 `true`。
如果该异常没有在生成器内部被捕获,则它将通过 `throw()` 的调用向上传播,对 `next()` 的后续调用将导致 `done` 属性为 `true`。

生成器具有 {{jsxref("Global_Objects/Generator/return","return(value)")}} 方法,返回给定的值并完成生成器本身
生成器的 {{jsxref("Generator/return", "return()")}} 方法可返回给定的值并终结这个生成器

{{PreviousNext("Web/JavaScript/Guide/Using_promises", "Web/JavaScript/Guide/Meta_programming")}}
{{PreviousNext("Web/JavaScript/Guide/Typed_arrays", "Web/JavaScript/Guide/Meta_programming")}}