aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/web/javascript/guide/iterators_and_generators/index.html
blob: 9e5bbe3cbdea282927d09a55b287385ad811cd63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
---
title: 迭代器和生成器
slug: Web/JavaScript/Guide/Iterators_and_Generators
tags:
  - Generator
  - Guide
  - Intermediate
  - Iterator
  - JavaScript
  - 中级
  - 生成器
  - 迭代器
translation_of: Web/JavaScript/Guide/Iterators_and_Generators
---
<div>{{jsSidebar("JavaScript Guide")}} {{PreviousNext("Web/JavaScript/Guide/Using_promises", "Web/JavaScript/Guide/Meta_programming")}}</div>

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

<p>若想了解更多详情,请参考:</p>

<ul>
 <li><a href="/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols">迭代协议</a></li>
 <li>{{jsxref("Statements/for...of","for...of")}}</li>
 <li>{{jsxref("Statements/function*","function*")}}{{jsxref("Generator")}}</li>
 <li>{{jsxref("Operators/yield","yield")}}{{jsxref("Operators/yield*","yield*")}}</li>
</ul>

<h2 id="迭代器">迭代器</h2>

<p>在 JavaScript 中,<strong>迭代器</strong>是一个对象,它定义一个序列,并在终止时可能返回一个返回值。 更具体地说,迭代器是通过使用 <code>next()</code> 方法实现 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterator_protocol">Iterator protocol</a> 的任何一个对象,该方法返回具有两个属性的对象: <code>value</code>,这是序列中的 next 值;和 <code>done</code> ,如果已经迭代到序列中的最后一个值,则它为 <code>true</code> 。如果 <code>value</code> 和 <code>done</code> 一起存在,则它是迭代器的返回值。</p>

<p>一旦创建,迭代器对象可以通过重复调用next()显式地迭代。 迭代一个迭代器被称为消耗了这个迭代器,因为它通常只能执行一次。 在产生终止值之后,对next()的额外调用应该继续返回{done:true}。<br>
 <br>
 Javascript中最常见的迭代器是Array迭代器,它只是按顺序返回关联数组中的每个值。 虽然很容易想象所有迭代器都可以表示为数组,但事实并非如此。 数组必须完整分配,但迭代器仅在必要时使用,因此可以表示无限大小的序列,例如0和无穷大之间的整数范围。</p>

<p><br>
 这是一个可以做到这一点的例子。 它允许创建一个简单的范围迭代器,它定义了从开始(包括)到结束(独占)间隔步长的整数序列。 它的最终返回值是它创建的序列的大小,由变量iterationCount跟踪。</p>

<pre class="brush: js">function makeRangeIterator(start = 0, end = Infinity, step = 1) {
    let nextIndex = start;
    let iterationCount = 0;

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

<p>使用这个迭代器看起来像这样:</p>

<pre class="brush: js">let it = makeRangeIterator(1, 10, 2);

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

console.log("Iterated over sequence of size: ", result.value); // 5

</pre>

<div class="note">
<p><strong>备注:</strong>反射性地知道特定对象是否是迭代器是不可能的。 如果您需要这样做,请使用 <a href="#Iterables">Iterables</a>.</p>
</div>

<h2 id="生成器函数">生成器函数</h2>

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

<p>可以根据需要多次调用该函数,并且每次都返回一个新的Generator,但每个Generator只能迭代一次。</p>

<p>我们现在可以调整上面的例子了。 此代码的行为是相同的,但实现更容易编写和读取。</p>

<pre class="brush: js">function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
    for (let i = start; i &lt; end; i += step) {
        yield i;
    }
}
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}
</pre>

<h2 id="可迭代对象">可迭代对象</h2>

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

<p>为了实现<strong>可迭代</strong>,一个对象必须实现 <strong>@@iterator</strong> 方法,这意味着这个对象(或其<a href="/zh-CN/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain">原型链</a>中的任意一个对象)必须具有一个带 {{jsxref("Symbol.iterator")}} 键(key)的属性。</p>

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

<h3 id="自定义的可迭代对象">自定义的可迭代对象</h3>

<p>我们可以像这样实现自己的可迭代对象:</p>

<pre class="brush: js">var myIterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
}

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

// 或者

[...myIterable]; // [1, 2, 3]
</pre>

<h3 id="内置可迭代对象">内置可迭代对象</h3>

<p>{{jsxref("String")}}{{jsxref("Array")}}{{jsxref("TypedArray")}}{{jsxref("Map")}}{{jsxref("Set")}} 都是内置可迭代对象,因为它们的原型对象都拥有一个 {{jsxref("Symbol.iterator")}} 方法。</p>

<h3 id="用于可迭代对象的语法">用于可迭代对象的语法</h3>

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

<pre class="brush: js">for (let 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"

</pre>

<h2 id="高级生成器">高级生成器</h2>

<p>生成器会按需计算它们的产生值,这使得它们能够有效的表示一个计算成本很高的序列,甚至是如上所示的一个无限序列。</p>

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

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

<pre class="brush: js">function* fibonacci() {
  var fn1 = 0;
  var fn2 = 1;
  while (true) {
    var current = fn1;
    fn1 = fn2;
    fn2 = current + fn1;
    var reset = yield current;
    if (reset) {
        fn1 = 0;
        fn2 = 1;
    }
  }
}

var 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</pre>

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

<p>如果在抛出的异常处理期间没有遇到 <code>yield</code>,则异常将通过调用 <code>throw()</code> 向上传播,对 <code>next()</code> 的后续调用将导致 <code>done</code> 属性为 <code>true</code></p>

<p>生成器具有 {{jsxref("Global_Objects/Generator/return","return(value)")}} 方法,返回给定的值并完成生成器本身。</p>

<p>{{PreviousNext("Web/JavaScript/Guide/Using_promises", "Web/JavaScript/Guide/Meta_programming")}}</p>