Skip to content

Commit 5d00bad

Browse files
authored
Merge pull request javascript-tutorial#628 from yuhengshen/master
update "1-js/12-generators-iterators/1-generators"
2 parents 1814aaa + 243553a commit 5d00bad

1 file changed

Lines changed: 65 additions & 73 deletions

File tree

  • 1-js/12-generators-iterators/1-generators

1-js/12-generators-iterators/1-generators/article.md

Lines changed: 65 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Generators
22

3-
通常情况下,函数都只会返回一个值或者什么也不返回
3+
常规函数只会返回一个具体值(或者 `undefined`
44

5-
Generators 可以按需一个个返回(“yield”)多个值,可以是无限数量个值。它们与 [iterables](info:iterable) 配合使用,可以轻松创建数据流。
5+
Generators 可以按需逐个生成(“yield”)多个值。它们与 [iterables](info:iterable) 配合使用,可以轻松创建数据流。
66

77
## Generator 函数
88

@@ -18,24 +18,35 @@ function* generateSequence() {
1818
}
1919
```
2020

21-
“generator 函数”这个术语听起来有点误导,因为我们在调用它时候并不会执行代码。相反,他返回一个特殊的对象,我们称为“generator 对象”。
21+
“generator 函数”与常规函数的运行表现有所不同,当执行“generator 函数”时,它并不直接执行完**函数体**的代码,而是返回一个特殊的对象,即“generator 对象”,来管理执行流程
2222

23-
因此,它是一种“generator 构造器函数”。
23+
来打印一下这种对象:
2424

2525
```js
26-
// “generator 函数”创建“generator 对象”
26+
function* generateSequence() {
27+
yield 1;
28+
yield 2;
29+
return 3;
30+
}
31+
32+
// "generator function"(指 generateSequence()) 创建了一个 "generator 对象"
2733
let generator = generateSequence();
34+
*!*
35+
alert(generator); // [object Generator]
36+
*/!*
2837
```
2938

30-
`generator` 对象类似于“冻结函数调用(frozen function call)”:
39+
上面的代码中,**函数体**代码还没有开始执行:
3140

3241
![](generateSequence-1.svg)
3342

34-
在创建后,代码在一开始就暂停执行
43+
generator 对象的主要方法是 `next()`。被调用时,它会恢复上面的执行过程直到最近的 `yield <value>` 语句( `value` 可以省略,默认为 `undefined` )。然后代码再次暂停执行,并将值返回给外部代码
3544

36-
Generator 的主要方法是 `next()`。调用它后,就会恢复上面的执行过程直到最近的 `yield <value>` 语句。然后代码再次暂停执行,并将值返回到外部代码。
45+
`next()` 调用结果总是一个包含两个属性的对象:
46+
- `value`: “generator 函数”每次 **产出(yielded)** 的值。(译者注:yield翻译为产出,是为了配合 **生成器(generator)** 的语义。)
47+
- `done`: `true` 表示“generator 函数”已经执行完成,否则为 `false`
3748

38-
例如,这里我们创建了 generator 并获取其第一个 yielded 值
49+
举个例子,下面我们创建一个 generator 并获取其第一个产出的值
3950

4051
```js run
4152
function* generateSequence() {
@@ -53,15 +64,11 @@ let one = generator.next();
5364
alert(JSON.stringify(one)); // {value: 1, done: false}
5465
```
5566

56-
`next()` 的结果总是一个对象:
57-
- `value`:yielded 值。
58-
- `done`:如果代码没有执行完,其值为 `false`,否则就是 `true`
59-
60-
截至目前,我们只获得了第一个值:
67+
截至目前,我们只获得了第一个值,函数体停在了第二行:
6168

6269
![](generateSequence-2.svg)
6370

64-
我们再次调用 `generator.next()`。代码恢复执行并返回下一个 `yield`
71+
再次调用 `generator.next()`。代码恢复执行并返回下一个 `yield` 的产出值
6572

6673
```js
6774
let two = generator.next();
@@ -81,23 +88,19 @@ alert(JSON.stringify(three)); // {value: 3, *!*done: true*/!*}
8188

8289
![](generateSequence-4.svg)
8390

84-
现在,generator 已经执行完成了。我们通过 `done:true` 和处理的最终结果 `value:3` 可以看出来。
85-
86-
此时如果再调用 `generator.next()` 将不起任何作用。如果我们还是执行此语句,那么它将会返回相同的对象:`{done: true}`
91+
我们通过 `done:true` 可以看出函数执行完成了,此时 `value:3` 作为函数执行的最终结果。
8792

88-
对于 generator 来说,没有办法去“回滚”它的操作。但是我们可以通过调用 `generateSequence()` 来创建另一个 generator。
89-
90-
到目前为止,最重要的是要理解 generator 函数与常规函数不同,generator 函数不运行代码。它们是作为“generator 工厂”。运行 `function*` 返回一个 generator,然后我们调用它获得需要的值。
93+
再调用 `generator.next()` 已经没有什么意义了。这将总是返回相同的对象:`{done: true}`
9194

9295
```smart header="`function* f(…)` 或者 `function *f(…)`?"
9396
这是一个小的书写习惯问题,两者的语法都是正确的。
9497

95-
但是通常首选第一种语法,因为星号 `*` 表示它是一个 generator 函数,它描述的是函数种类而不是名称,因此它仍应使用 `function` 关键字
98+
但是通常首选第一种语法,因为星号 `*` 表示它是一个 generator 函数,它描述的是函数种类而不是名称,因此`*`应该和 `function` 关键字紧贴一起
9699
```
97100
98101
## Generators 是可迭代的
99102
100-
你可能通过 `next()` 方法了解到 generator 是[可迭代](info:iterable)的。
103+
看到 `next()` 方法,或许你都猜到了 generator 是 [可迭代](info:iterable) 的。(译者注:`next()` 是 iterator 的必要方法)
101104
102105
我们可以通过 `for..of` 循环迭代所有值:
103106
@@ -115,11 +118,11 @@ for(let value of generator) {
115118
}
116119
```
117120

118-
这样的方法看上去要比一个个调用 `.next().value` 好得多,不是吗
121+
`for ... of` 写法是不是比 `.next().value` 优雅多了
119122

120123
……但是请注意:上面的迭代例子中,它先显示 `1`,然后是 `2`。它不会显示 `3`
121124

122-
这是因为当 `done: true` 时,for-of 循环会忽略最后一个 `value`。因此,如果我们想要通过 `for..of` 循环显示所有结果,我们必须使用 `yield` 而不是 `return` 返回它们
125+
这是因为当 `done: true` 时,for-of 循环会忽略最后一个 `value`。因此,如果我们想要通过 `for..of` 循环显示所有结果时,我们必须使用 `yield` 而不是 `return`
123126

124127
```js run
125128
function* generateSequence() {
@@ -137,7 +140,7 @@ for(let value of generator) {
137140
}
138141
```
139142

140-
当然,由于 generators 是可迭代的,我们可以调用所有相关的函数,例如:spread 操作 `...`
143+
由于 generators 是可迭代的,我们可以充分发挥 ES6 中 iterator 的特性,例如:spread 操作 `...`
141144

142145
```js run
143146
function* generateSequence() {
@@ -151,7 +154,7 @@ let sequence = [0, ...generateSequence()];
151154
alert(sequence); // 0, 1, 2, 3
152155
```
153156

154-
在上面的代码中,`...generateSequence()` 将 iterable 转换为 item 的数组(关于 spread 操作可以参见相关章节 [](info:rest-parameters-spread-operator#spread-operator))。
157+
在上面的代码中,`...generateSequence()` 将可迭代的“generator 对象”转换为了一个数组(关于 spread 操作可以参见相关章节 [](info:rest-parameters-spread-operator#spread-operator))。
155158

156159
## 使用 generator 进行迭代
157160

@@ -167,14 +170,14 @@ let range = {
167170
// for..of range 在一开始就调用一次这个方法
168171
[Symbol.iterator]() {
169172
// ……它返回 iterator 对象:
170-
// 向前,for..of 仅适用于该对象,请求下一个值
173+
// 后续的操作中, for..of 将只针对这个 iterator 对象,通过不断的调用 next() 来获取下一个值
171174
return {
172175
current: this.from,
173176
last: this.to,
174177

175178
// for..of 在每次迭代的时候都会调用 next()
176179
next() {
177-
// 它应该返回对象 {done:.., value :...}
180+
// 必须返回特定结构的对象: {done:.., value :...}
178181
if (this.current <= this.last) {
179182
return { done: false, value: this.current++ };
180183
} else {
@@ -185,28 +188,13 @@ let range = {
185188
}
186189
};
187190

191+
// 迭代整个 range 对象,返回从 `range.from` 到 `range.to` 范围的所有数字的数组。
188192
alert([...range]); // 1,2,3,4,5
189193
```
190194

191-
使用 generator 来生成可迭代序列更简单,更优雅:
192-
193-
```js run
194-
function* generateSequence(start, end) {
195-
for (let i = start; i <= end; i++) {
196-
yield i;
197-
}
198-
}
199-
200-
let sequence = [...generateSequence(1,5)];
201-
202-
alert(sequence); // 1, 2, 3, 4, 5
203-
```
204-
205-
## 转换 Symbol.iterator 为 generator
206-
207-
我们可以通过提供一个 generator 作为 `Symbol.iterator` 来向任何自定义对象添加 generator-style 的迭代。
195+
我们可以通过提供一个 generator 函数作为对象的 `Symbol.iterator` 来使任何对象可被迭代。
208196

209-
这是相同的 `range`,但是使用的是一个更紧凑的 iterator
197+
以下是使用的另一种更紧凑的写法的 `range` 对象
210198

211199
```js run
212200
let range = {
@@ -223,15 +211,15 @@ let range = {
223211
alert( [...range] ); // 1,2,3,4,5
224212
```
225213

226-
正常工作,因为 `range[Symbol.iterator]()` 现在返回一个 generator,而 generator 方法正是 `for..of` 所期待的
214+
代码正常工作,因为 `range[Symbol.iterator]()` 现在返回一个 generator,而 generator 方法正是 `for..of` 所期望的
227215
- 它具有 `.next()` 方法
228216
- 它以 `{value: ..., done: true/false}` 的形式返回值
229217

230218
当然,这不是巧合,Generators 被添加到 JavaScript 语言中时也考虑了 iterators,以便更容易实现。
231219

232-
带有 generator 的最后一个变体比 `range` 的原始可迭代代码简洁得多,并保持了相同的功能
220+
带有 generator `range` 对象比的原始可迭代代码简洁得多,还保持了功能的一致
233221

234-
```smart header="Generators 可能永远 generate 值"
222+
```smart header="Generators 可以永远产生值"
235223
在上面的例子中,我们生成了有限序列,但是我们也可以创建一个生成无限序列的 generator,它可以一直 yield 值。例如,无序的伪随机数序列。
236224
237225
这种情况下的 `for..of` generator 需要一个 `break`(或者 `return`)语句,否则循环将永远重复并挂起。
@@ -241,18 +229,26 @@ alert( [...range] ); // 1,2,3,4,5
241229

242230
Generator 组合是 generator 的一个特殊功能,它可以显式地将 generator “嵌入”到一起。
243231

244-
例如,我们想要生成一个序列:
245-
- 数字 `0..9`(ASCII 可显示字符代码为 48..57),
246-
- 后跟字母 `a..z`(ASCII 可显示字符代码为 65..90)
247-
- 后跟大写字母 `A..Z`(ASCII 可显示字符代码为 97..122)
232+
如下,我们有个生成数字序列的函数:
248233

249-
我们可以使用序列,比如通过从中选择字符来创建密码(也可以添加语法字符),但是我们先生成它。
234+
```js
235+
function* generateSequence(start, end) {
236+
for (let i = start; i <= end; i++) yield i;
237+
}
238+
```
239+
240+
接着,我们复用这个函数来生成更加复杂的包含三部分的序列:
241+
- 第一部分为数字 `0..9`(ASCII 可显示字符代码为 48..57),
242+
- 第二部分为大写字母字母 `A..Z`(ASCII 可显示字符代码为 65..90)
243+
- 第三部分为小写字母 `a...z`(ASCII 可显示字符代码为 97..122)
250244

251-
我们已经有了 `function* generateSequence(start, end)`。让我们重复使用它来一个个地传递 3 个序列,它真是我们所需要的
245+
我们可以在这个序列中选择字符来创建密码(也可以添加其他特殊字符),现在先编写这个生成器
252246

253-
在普通函数中,为了将多个其他函数的结果组合到一起,我们先调用它们,然后将他们的结果存储起来,最后将它们合并到一起
247+
在常规函数的调用中,为了组合多个函数的结果,我们需要先依次调用它们,并分别将他们的结果存储起来,最后统一将它们合并到一起
254248

255-
对于 generators,我们可以更好地去实现,就像这样:
249+
对于 generators 而言,我们可以使用 `yield*` 这个语法来将一个 generator 嵌入(组合)到另一个 generator 中:
250+
251+
组合式 generator 的例子:
256252

257253
```js run
258254
function* generateSequence(start, end) {
@@ -283,9 +279,9 @@ for(let code of generatePasswordCodes()) {
283279
alert(str); // 0..9A..Za..z
284280
```
285281

286-
示例中的特殊 `yield*` 指令负责组合。它将执行**委托**给另一个 generator。或者简单来说就是 `yield* gen` 迭代 generator `gen` 并显式地将其 yield 结果转发到外部。好像这些值是由外部 generator yield 一样
282+
示例中的 `yield*` 指令负责将执行**委托**给另一个 generator。或者简单来说就是 `yield* gen` 迭代了名为 `gen`generator 并显式地将 `gen` yield 的结果转发到最外部。好像这些值是由外部的 generator yield 的一样
287283

288-
结果就像是我们从嵌套的 generators 内联的代码一样
284+
执行结果和我们将嵌套的 generators 中的代码直接内联到外层generator一样
289285

290286
```js run
291287
function* generateSequence(start, end) {
@@ -316,19 +312,15 @@ for(let code of generateAlphaNum()) {
316312
alert(str); // 0..9A..Za..z
317313
```
318314

319-
Generator 组合是将一个 generator 流插入到另一个 generator 的自然的方式。
320-
321-
即使来自嵌套 generator 的值的流是无限的,它也可以正常工作。它很简单,不需要使用额外的内存来存储中间结果。
322-
323-
## “yield” 双向路径(two-way road)
315+
**Generator 组合**是将一个 generator 流自然地插入到另一个 generator 的方式。它不需要使用额外的内存来存储中间结果。
324316

325-
直到此时,generators 就像“固醇(steroids)上的 iterators”。这就是它经常被使用的方式。
317+
## “yield” 双向路径(two-way street)
326318

327-
但是实际上,generators 要更强大,更灵活
319+
目前看来,generators 和可迭代对象非常相似,仅仅是其产生 value 的语法有所不同。但实际上,generators 更加高效和灵活
328320

329-
这是因为 `yield` 是一个双向路径:它不仅向外面返回结果,而且可以传递 generator 内的值
321+
这是因为 `yield` 是一个双向路径:它不仅向外面返回结果,而且可以将外部的值传递到 generator
330322

331-
为此,我们应该使用参数 arg 调用 `generator.next(arg)`。这个参数就成了 `yield` 的结果
323+
调用 `generator.next(arg)`,我们就能将值 `arg` 传递到了 generator 内部。这个 `arg` 参数会变成 `yield` 语句的结果(返回值)
332324

333325
我们来看一个例子:
334326

@@ -364,9 +356,9 @@ generator.next(4); // --> 向 generator 传入结果
364356
setTimeout(() => generator.next(4), 1000);
365357
```
366358

367-
我们可以看到,与普通函数不同,generators 和调用代码可以通过传递 `next/yield` 中的值来交换结果
359+
我们可以看到,与常规函数不同,generators 内部和外部调用环境可以通过 `next/yield` 来传递值,以交换结果
368360

369-
为了使事情浅显易懂,我们来看另一个有更多调用的例子:
361+
为了使以上要点浅显易懂,我们来看另一个有更多调用的例子:
370362

371363
```js run
372364
function* gen() {
@@ -406,7 +398,7 @@ alert( generator.next(9).done ); // true
406398

407399
……但是它也可以在那里发起(抛出)错误。这很自然,因为错误本身也是一种结果。
408400

409-
要向 `yield` 传递错误,我们应该调用 `generator.throw(err)`在那种情况下`err` `yield` 一起被抛出
401+
要向 `yield` 传递错误,我们应该调用 `generator.throw(err)`然后`err` 将在对应的 `yield` 那一行被抛出
410402

411403
例如,`"2 + 2?"` 的 yield 导致一个错误:
412404

@@ -454,7 +446,7 @@ try {
454446
*/!*
455447
```
456448

457-
如果我们在那里捕获错误,那么像往常一样,它会转到外部代码(如果有的话),如果没有捕获,则会结束脚本。
449+
通常,如果我们没有在那里捕获错误,它会将错误转到外部代码(如果有的话),如果外部也没有捕获错误,则会结束脚本。
458450

459451
## 总结
460452

0 commit comments

Comments
 (0)