11# Generator 函数
22
3- ## 语法
4-
5- ### 简介
3+ ## 简介
64
75所谓Generator,有多种理解角度。首先,可以把它理解成一个函数的内部状态的遍历器,每调用一次,函数的内部状态发生一次改变(可以理解成发生某些事件)。ES6引入Generator函数,作用就是可以完全控制函数的内部状态的变化,依次遍历这些状态。
86
1311function * helloWorldGenerator () {
1412 yield ' hello' ;
1513 yield ' world' ;
16- return ' ending' ;
14+ return ' ending' ;
1715}
1816
1917var hw = helloWorldGenerator ();
@@ -26,7 +24,7 @@ var hw = helloWorldGenerator();
2624
2725``` javascript
2826
29- hw .next ()
27+ hw .next ()
3028// { value: 'hello', done: false }
3129
3230hw .next ()
@@ -52,20 +50,22 @@ hw.next()
5250
5351总结一下,Generator函数使用iterator接口,每次调用next方法的返回值,就是一个标准的iterator返回值:有着value和done两个属性的对象。其中,value是yield语句后面那个表达式的值,done是一个布尔值,表示是否遍历结束。
5452
55- 上一章说过,任意一个对象的Symbol.iterator属性,等于该对象的遍历器函数,即调用该函数会返回该对象的一个遍历器。由于Generator函数调用后返回自身的遍历器,所以Generator函数就是自身的遍历器函数,即它的Symbol.iterator属性指向自身 。
53+ 上一章说过,任意一个对象的Symbol.iterator属性,等于该对象的遍历器函数,即调用该函数会返回该对象的一个遍历器。遍历器本身也是一个对象,它的Symbol.iterator属性执行后,返回自身 。
5654
5755``` javascript
5856
5957function * gen (){
6058 // some code
6159}
6260
63- gen[Symbol .iterator ]() === gen
61+ var g = gen ();
62+
63+ g[Symbol .iterator ]() === g
6464// true
6565
6666```
6767
68- 上面代码中,gen是一个Generator函数,它的Symbol.iterator属性就指向它自己 。
68+ 上面代码中,gen是一个Generator函数,调用它会生成一个遍历器g。遍历器g的Symbol.iterator属性是一个遍历器函数,执行后返回它自己 。
6969
7070由于Generator函数返回的遍历器,只有调用next方法才会遍历下一个成员,所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志,next方法遇到yield,就会暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回对象的value属性的值。当下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。如果没有再遇到新的yield语句,就一直运行到函数结束,将return语句后面的表达式的值,作为value属性的值,如果该函数没有return语句,则value属性的值为undefined。另一方面,由于yield后面的表达式,直到调用next方法时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
7171
@@ -82,7 +82,7 @@ function* f() {
8282var generator = f ();
8383
8484setTimeout (function () {
85- generator .next ()
85+ generator .next ()
8686}, 2000 );
8787
8888```
@@ -111,7 +111,7 @@ var arr = [1, [[2, 3], 4], [5, 6]];
111111var flat = function * (a ){
112112 a .forEach (function (item ){
113113 if (typeof item !== ' number' ){
114- yield * flat (item);
114+ yield * flat (item);
115115 } else {
116116 yield item;
117117 }
@@ -135,7 +135,7 @@ var flat = function* (a){
135135 for (var i = 0 ;i< length;i++ ){
136136 var item = a[i];
137137 if (typeof item !== ' number' ){
138- yield * flat (item);
138+ yield * flat (item);
139139 } else {
140140 yield item;
141141 }
@@ -149,7 +149,7 @@ for (var f of flat(arr)){
149149
150150` ` `
151151
152- ### next方法的参数
152+ ## next方法的参数
153153
154154yield语句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。
155155
@@ -199,7 +199,7 @@ it.next(13)
199199
200200注意,由于next方法的参数表示上一个yield语句的返回值,所以第一次使用next方法时,不能带有参数。V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。
201201
202- ### for...of循环
202+ ## for...of循环
203203
204204for...of循环可以自动遍历Generator函数,且此时不再需要调用next方法。
205205
@@ -228,49 +228,49 @@ for (let v of foo()) {
228228` ` ` javascript
229229
230230function * fibonacci () {
231- let [prev, curr] = [0 , 1 ];
232- for (;;) {
233- [prev, curr] = [curr, prev + curr];
234- yield curr;
235- }
231+ let [prev, curr] = [0 , 1 ];
232+ for (;;) {
233+ [prev, curr] = [curr, prev + curr];
234+ yield curr;
235+ }
236236}
237237
238238for (let n of fibonacci ()) {
239- if (n > 1000 ) break ;
240- console .log (n);
239+ if (n > 1000 ) break ;
240+ console .log (n);
241241}
242242
243243` ` `
244244
245245从上面代码可见,使用for...of语句时不需要使用next方法。
246246
247- ### throw方法
247+ ## throw方法
248248
249249Generator函数还有一个特点,它可以在函数体外抛出错误,然后在函数体内捕获。
250250
251251` ` ` javascript
252252
253253var g = function * () {
254- while (true ) {
255- try {
256- yield ;
257- } catch (e) {
258- if (e != ' a' ) {
259- throw e;
260- }
261- console .log (' 内部捕获' , e);
262- }
254+ while (true ) {
255+ try {
256+ yield ;
257+ } catch (e) {
258+ if (e != ' a' ) {
259+ throw e;
260+ }
261+ console .log (' 内部捕获' , e);
263262 }
263+ }
264264};
265265
266266var i = g ();
267267i .next ();
268268
269269try {
270- i .throw (' a' );
271- i .throw (' b' );
270+ i .throw (' a' );
271+ i .throw (' b' );
272272} catch (e) {
273- console .log (' 外部捕获' , e);
273+ console .log (' 外部捕获' , e);
274274}
275275// 内部捕获 a
276276// 外部捕获 b
@@ -279,6 +279,101 @@ try {
279279
280280上面代码中,遍历器i连续抛出两个错误。第一个错误被Generator函数体内的catch捕获,然后Generator函数执行完成,于是第二个错误被函数体外的catch捕获。
281281
282+ 注意,上面代码的错误,是用遍历器的throw方法抛出的,而不是用throw命令抛出的。后者只能被函数体外的catch语句捕获。
283+
284+ ` ` ` javascript
285+ var g = function * () {
286+ while (true ) {
287+ try {
288+ yield ;
289+ } catch (e) {
290+ if (e != ' a' ) {
291+ throw e;
292+ }
293+ console .log (' 内部捕获' , e);
294+ }
295+ }
296+ };
297+
298+ var i = g ();
299+ i .next ();
300+
301+ try {
302+ throw new Error (' a' );
303+ throw new Error (' b' );
304+ } catch (e) {
305+ console .log (' 外部捕获' , e);
306+ }
307+ // 外部捕获 [Error: a]
308+ ` ` `
309+
310+ 上面代码之所以只捕获了a,是因为函数体外的catch语句块,捕获了抛出的a错误以后,就不会再继续执行try语句块了。
311+
312+ 如果遍历器函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获。
313+
314+ ` ` ` javascript
315+ var g = function * () {
316+ while (true ) {
317+ yield ;
318+ console .log (' 内部捕获' , e);
319+ }
320+ };
321+
322+ var i = g ();
323+ i .next ();
324+
325+ try {
326+ i .throw (' a' );
327+ i .throw (' b' );
328+ } catch (e) {
329+ console .log (' 外部捕获' , e);
330+ }
331+ // 外部捕获 a
332+ ` ` `
333+
334+ 上面代码中,遍历器函数g内部,没有部署try...catch代码块,所以抛出的错误直接被外部catch代码块捕获。
335+
336+ 如果遍历器函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历,否则遍历直接终止。
337+
338+ ` ` ` javascript
339+ var gen = function * gen (){
340+ yield console .log (' hello' );
341+ yield console .log (' world' );
342+ }
343+
344+ var g = gen ();
345+ g .next ();
346+
347+ try {
348+ g .throw ();
349+ } catch (e) {
350+ g .next ();
351+ }
352+ // hello
353+ ` ` `
354+
355+ 上面代码只输出hello就结束了,因为第二次调用next方法时,遍历器状态已经变成终止了。但是,如果使用throw方法抛出错误,不会影响遍历器状态。
356+
357+ ` ` ` javascript
358+ var gen = function * gen (){
359+ yield console .log (' hello' );
360+ yield console .log (' world' );
361+ }
362+
363+ var g = gen ();
364+ g .next ();
365+
366+ try {
367+ throw new Error ();
368+ } catch (e) {
369+ g .next ();
370+ }
371+ // hello
372+ // world
373+ ` ` `
374+
375+ 上面代码中,throw命令抛出的错误不会影响到遍历器的状态,所以两次执行next方法,都取到了正确的操作。
376+
282377这种函数体内捕获错误的机制,大大方便了对错误的处理。如果使用回调函数的写法,想要捕获多个错误,就不得不为每个函数写一个错误处理语句。
283378
284379` ` ` javascript
@@ -287,17 +382,17 @@ foo('a', function (a) {
287382 if (a .error ) {
288383 throw new Error (a .error );
289384 }
290-
385+
291386 foo (' b' , function (b ) {
292387 if (b .error ) {
293388 throw new Error (b .error );
294389 }
295-
390+
296391 foo (' c' , function (c ) {
297392 if (c .error ) {
298393 throw new Error (c .error );
299394 }
300-
395+
301396 console .log (a, b, c);
302397 });
303398 });
@@ -324,42 +419,25 @@ function* g(){
324419
325420` ` `
326421
327- 如果Generator函数内部没有定义catch,那么throw方法抛出的错误,将被函数体的catch捕获。
328-
329- ` ` ` javascript
330-
331- function * foo () { }
332-
333- var it = foo ();
334- try {
335- it .throw ( " Oops!" );
336- } catch (err) {
337- console .log ( " Error: " + err ); // Error: Oops!
338- }
339-
340- ` ` `
341-
342- 上面代码中,foo函数内部没有任何语句,throw抛出的错误被函数体外的catch捕获。
343-
344422反过来,Generator函数内抛出的错误,也可以被函数体外的catch捕获。
345423
346424` ` ` javascript
347425
348426function * foo () {
349- var x = yield 3 ;
350- var y = x .toUpperCase ();
351- yield y;
427+ var x = yield 3 ;
428+ var y = x .toUpperCase ();
429+ yield y;
352430}
353431
354432var it = foo ();
355433
356434it .next (); // { value:3, done:false }
357435
358436try {
359- it .next ( 42 );
437+ it .next (42 );
360438}
361439catch (err) {
362- console .log ( err );
440+ console .log (err);
363441}
364442
365443` ` `
@@ -408,12 +486,12 @@ log(g());
408486// fixing generator { value: 1, done: false }
409487// fixing generator { value: 1, done: false }
410488// caller done
411-
489+
412490` ` `
413491
414492上面代码在Generator函数g抛出错误以后,再调用next方法,就不再执行下去了,一直停留在上一次的状态。
415493
416- ### yield*语句
494+ ## yield*语句
417495
418496如果yield命令后面跟的是一个遍历器,需要在yield命令后面加上星号,表明它返回的是一个遍历器。这被称为yield*语句。
419497
@@ -478,7 +556,7 @@ var it = bar();
478556it .next (); //
479557it .next (); //
480558it .next (); //
481- it .next (); // "v: foo"
559+ it .next (); // "v: foo"
482560it .next (); //
483561
484562` ` `
@@ -546,15 +624,15 @@ let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
546624// 遍历二叉树
547625var result = [];
548626for (let node of inorder (tree)) {
549- result .push (node);
627+ result .push (node);
550628}
551629
552630result
553631// ['a', 'b', 'c', 'd', 'e', 'f', 'g']
554632
555633` ` `
556634
557- ### 作为对象属性的Generator函数
635+ ## 作为对象属性的Generator函数
558636
559637如果一个对象的属性是Generator函数,可以简写成下面的形式。
560638
0 commit comments