Skip to content

Commit 73e5815

Browse files
committed
edit generator
1 parent 56cd641 commit 73e5815

File tree

2 files changed

+129
-38
lines changed

2 files changed

+129
-38
lines changed

docs/destructuring.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ if ([1][0] === undefined) {
167167

168168
```javascript
169169
let [x = 1, y = x] = []; // x=1; y=1
170-
let [x = 1, y = x] = [2]; // x=1; y=2
170+
let [x = 1, y = x] = [2]; // x=2; y=2
171171
let [x = 1, y = x] = [1, 2]; // x=1; y=2
172172
let [x = y, y = 1] = []; // ReferenceError
173173
```

docs/generator.md

Lines changed: 128 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ hw.next()
5454

5555
总结一下,调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的`next`方法,就会返回一个有着`value``done`两个属性的对象。`value`属性表示当前的内部状态的值,是`yield`语句后面那个表达式的值;`done`属性是一个布尔值,表示是否遍历结束。
5656

57+
ES6没有规定,`function`关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过。
58+
59+
```javascript
60+
function * foo(x, y) { ··· }
61+
62+
function *foo(x, y) { ··· }
63+
64+
function* foo(x, y) { ··· }
65+
66+
function*foo(x, y) { ··· }
67+
```
68+
69+
由于Generator函数仍然是普通函数,所以一般的写法是上面的第三种,即星号紧跟在`function`关键字后面。本书也采用这种写法/。
70+
5771
### yield语句
5872

5973
由于Generator函数返回的遍历器对象,只有调用`next`方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。`yield`语句就是暂停标志。
@@ -219,36 +233,21 @@ function* foo(x) {
219233
}
220234

221235
var a = foo(5);
222-
223236
a.next() // Object{value:6, done:false}
224237
a.next() // Object{value:NaN, done:false}
225238
a.next() // Object{value:NaN, done:false}
239+
240+
var b = foo(5);
241+
b.next() // { value:6, done:false }
242+
b.next(12) // { value:8, done:false }
243+
b.next(13) // { value:42, done:true }
226244
```
227245
228246
上面代码中,第二次运行`next`方法的时候不带参数,导致y的值等于`2 * undefined`(即`NaN`),除以3以后还是`NaN`,因此返回对象的`value`属性也等于`NaN`。第三次运行`Next`方法的时候不带参数,所以`z`等于`undefined`,返回对象的`value`属性等于`5 + NaN + undefined`,即`NaN`
229247
230-
如果向`next`方法提供参数,返回结果就完全不一样了。
248+
如果向`next`方法提供参数,返回结果就完全不一样了。上面代码第一次调用`b``next`方法时,返回`x+1`的值6;第二次调用`next`方法,将上一次`yield`语句的值设为12,因此`y`等于24,返回`y / 3`的值8;第三次调用`next`方法,将上一次`yield`语句的值设为13,因此`z`等于13,这时`x`等于5,`y`等于24,所以`return`语句的值等于42。
231249
232-
```javascript
233-
function* foo(x) {
234-
var y = 2 * (yield (x + 1));
235-
var z = yield (y / 3);
236-
return (x + y + z);
237-
}
238-
239-
var it = foo(5);
240-
241-
it.next()
242-
// { value:6, done:false }
243-
it.next(12)
244-
// { value:8, done:false }
245-
it.next(13)
246-
// { value:42, done:true }
247-
```
248-
249-
上面代码第一次调用`next`方法时,返回`x+1`的值6;第二次调用`next`方法,将上一次`yield`语句的值设为12,因此`y`等于24,返回`y / 3`的值8;第三次调用`next`方法,将上一次`yield`语句的值设为13,因此`z`等于13,这时`x`等于5,`y`等于24,所以`return`语句的值等于42。
250-
251-
注意,由于`next`方法的参数表示上一个`yield`语句的返回值,所以第一次使用`next`方法时,不能带有参数。V8引擎直接忽略第一次使用`next`方法时的参数,只有从第二次使用`next`方法开始,参数才是有效的。
250+
注意,由于`next`方法的参数表示上一个`yield`语句的返回值,所以第一次使用`next`方法时,不能带有参数。V8引擎直接忽略第一次使用`next`方法时的参数,只有从第二次使用`next`方法开始,参数才是有效的。从语义上讲,第一个`next`方法用来启动遍历器对象,所以不用带有参数。
252251
253252
如果想要第一次调用`next`方法时,就能够输入值,可以在Generator函数外面再包一层。
254253
@@ -272,6 +271,27 @@ wrapped().next('hello!')
272271
273272
上面代码中,Generator函数如果不用`wrapper`先包一层,是无法第一次调用`next`方法,就输入参数的。
274273
274+
再看一个通过`next`方法的参数,向Generator函数内部输入值的例子。
275+
276+
```javascript
277+
function* dataConsumer() {
278+
console.log('Started');
279+
console.log(`1. ${yield}`);
280+
console.log(`2. ${yield}`);
281+
return 'result';
282+
}
283+
284+
let genObj = dataConsumer();
285+
genObj.next();
286+
// Started
287+
genObj.next('a')
288+
// 1. a
289+
genObj.next('b')
290+
// 2. b
291+
```
292+
293+
上面代码是一个很直观的例子,每次通过`next`方法向Generator函数输入值,然后打印出来。
294+
275295
## for...of循环
276296
277297
`for...of`循环可以自动遍历Generator函数,且此时不再需要调用`next`方法。
@@ -313,7 +333,7 @@ for (let n of fibonacci()) {
313333
314334
从上面代码可见,使用`for...of`语句时不需要使用next方法。
315335
316-
前面章节曾经介绍过,`for...of`循环、扩展运算符(...)、解构赋值和`Array.from`方法内部调用的,都是遍历器接口。这意味着,它们可以将Generator函数返回的Iterator对象,作为参数。
336+
前面章节曾经介绍过,`for...of`循环、扩展运算符(`...`)、解构赋值和`Array.from`方法内部调用的,都是遍历器接口。这意味着,它们可以将Generator函数返回的Iterator对象,作为参数。
317337
318338
```javascript
319339
function* numbers () {
@@ -350,7 +370,30 @@ function* objectEntries(obj) {
350370
}
351371

352372
let jane = { first: 'Jane', last: 'Doe' };
353-
for (let [key,value] of objectEntries(jane)) {
373+
374+
for (let [key, value] of objectEntries(jane)) {
375+
console.log(`${key}: ${value}`);
376+
}
377+
// first: Jane
378+
// last: Doe
379+
```
380+
381+
上面代码中,对象`jane`原生不具备Iterator接口,无法用`for...of`遍历。这时,我们通过Generator函数`objectEntries`为它加上遍历器接口,就可以用`for...of`遍历了。加上遍历器接口的另一种写法是,将Generator函数加到对象的`Symbol.iterator`属性上面。
382+
383+
```javascript
384+
function* objectEntries() {
385+
let propKeys = Object.keys(this);
386+
387+
for (let propKey of propKeys) {
388+
yield [propKey, this[propKey]];
389+
}
390+
}
391+
392+
let jane = { first: 'Jane', last: 'Doe' };
393+
394+
jane[Symbol.iterator] = objectEntries;
395+
396+
for (let [key, value] of jane) {
354397
console.log(`${key}: ${value}`);
355398
}
356399
// first: Jane
@@ -459,7 +502,7 @@ try {
459502
// hello
460503
```
461504
462-
上面代码只输出hello就结束了,因为第二次调用next方法时,遍历器状态已经变成终止了。但是,如果使用throw命令抛出错误,不会影响遍历器状态。
505+
上面代码只输出`hello`就结束了,因为第二次调用`next`方法时,遍历器状态已经变成终止了。但是,如果使用`throw`命令抛出错误,不会影响遍历器状态。
463506
464507
```javascript
465508
var gen = function* gen(){
@@ -765,7 +808,7 @@ for(let value of delegatingIterator) {
765808
766809
上面代码中,`delegatingIterator`是代理者,`delegatedIterator`是被代理者。由于`yield* delegatedIterator`语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,遍历了多个Generator函数,有递归的效果。
767810
768-
yield*语句等同于在Generator函数内部,部署一个for...of循环
811+
`yield*`语句等同于在Generator函数内部,部署一个`for...of`循环
769812
770813
```javascript
771814
function* concat(iter1, iter2) {
@@ -846,6 +889,26 @@ it.next()
846889
847890
上面代码在第四次调用`next`方法的时候,屏幕上会有输出,这是因为函数`foo``return`语句,向函数`bar`提供了返回值。
848891
892+
再看一个例子。
893+
894+
```javascript
895+
function* genFuncWithReturn() {
896+
yield 'a';
897+
yield 'b';
898+
return 'The result';
899+
}
900+
function* logReturned(genObj) {
901+
let result = yield* genObj;
902+
console.log(result);
903+
}
904+
905+
[...logReturned(genFuncWithReturn())]
906+
// The result
907+
// 值为 [ 'a', 'b' ]
908+
```
909+
910+
上面代码中,存在两次遍历。第一次是扩展运算符遍历函数`logReturned`返回的遍历器对象,第二次是`yield*`语句遍历函数`genFuncWithReturn`返回的遍历器对象。这两次遍历的效果是叠加的,最终表现为扩展运算符遍历函数`genFuncWithReturn`返回的遍历器对象。所以,最后的数据表达式得到的值等于`[ 'a', 'b' ]`。但是,函数`genFuncWithReturn``return`语句的返回值`The result`,会返回给函数`logReturned`内部的`result`变量,因此会有终端输出。
911+
849912
`yield*`命令可以很方便地取出嵌套数组的所有成员。
850913
851914
```javascript
@@ -935,9 +998,36 @@ let obj = {
935998
};
936999
```
9371000
938-
## 构造函数是Generator函数
1001+
## Generator函数的`this`
1002+
1003+
Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例,也继承了Generator函数的`prototype`对象上的方法。
1004+
1005+
```javascript
1006+
function* g() {}
1007+
1008+
g.prototype.hello = function () {
1009+
return 'hi!';
1010+
};
1011+
1012+
let obj = g();
1013+
1014+
obj instanceof g // true
1015+
obj.hello() // 'hi!'
1016+
```
1017+
1018+
上面代码表明,Generator函数`g`返回的遍历器`obj`,是`g`的实例,而且继承了`g.prototype`。但是,如果把`g`当作普通的构造函数,并不会生效,因为`g`返回的总是遍历器对象,而不是`this`对象。
1019+
1020+
```javascript
1021+
function* g() {
1022+
this.a = 11;
1023+
}
1024+
1025+
let obj = g();
1026+
obj.a // undefined
1027+
```
1028+
1029+
上面代码中,Generator函数`g``this`对象上面添加了一个属性`a`,但是`obj`对象拿不到这个属性。
9391030
940-
这一节讨论一种特殊情况:构造函数是Generator函数。
9411031
9421032
```javascript
9431033
function* F(){
@@ -955,23 +1045,24 @@ function* F(){
9551045
9561046
上面代码中,由于`new F()`返回的是一个Iterator对象,具有next方法,所以上面的表达式为true。
9571047
958-
那么,这个时候怎么生成对象实例呢?
959-
960-
我们知道,如果构造函数调用时,没有使用new命令,那么内部的this对象,绑定当前构造函数所在的对象(比如window对象)。因此,可以生成一个空对象,使用bind方法绑定F内部的this。这样,构造函数调用以后,这个空对象就是F的实例对象了。
1048+
如果要把Generator函数当作正常的构造函数使用,可以采用下面的变通方法。首先,生成一个空对象,使用`bind`方法绑定Generator函数内部的`this`。这样,构造函数调用以后,这个空对象就是Generator函数的实例对象了。
9611049
9621050
```javascript
1051+
function* F(){
1052+
yield this.x = 2;
1053+
yield this.y = 3;
1054+
}
9631055
var obj = {};
9641056
var f = F.bind(obj)();
9651057

966-
f.next();
967-
f.next();
968-
f.next();
1058+
f.next(); // Object {value: 2, done: false}
1059+
f.next(); // Object {value: 3, done: false}
1060+
f.next(); // Object {value: undefined, done: true}
9691061

970-
console.log(obj);
971-
// { x: 2, y: 3 }
1062+
obj // { x: 2, y: 3 }
9721063
```
9731064
974-
上面代码中,首先是F内部的this对象绑定obj对象,然后调用它,返回一个Iterator对象。这个对象执行三次next方法(因为F内部有两个yield语句),完成F内部所有代码的运行。这时,所有内部属性都绑定在obj对象上了,因此obj对象也就成了F的实例
1065+
上面代码中,首先是`F`内部的`this`对象绑定`obj`对象,然后调用它,返回一个Iterator对象。这个对象执行三次`next`方法(因为`F`内部有两个`yield`语句),完成F内部所有代码的运行。这时,所有内部属性都绑定在`obj`对象上了,因此`obj`对象也就成了`F`的实例
9751066
9761067
## Generator函数推导
9771068

0 commit comments

Comments
 (0)