ES6-Generator

依赖文件地址 :https://github.com/chanceLe/ES6-Basic-Syntax/tree/master/js

  1 <!DOCTYPE html>
  2 <html>
  3     <head>
  4         <meta charset="UTF-8">
  5         <title>[es6]-14-Generator函数</title>
  6         <script src="./js/browser.js"></script>
  7         <script src="./js/babel-pollyfill.js"></script>
  8         <!--引入babel-pollyfill的原因是,默认的babel不支持Generator-->
  9         <!--<script src="https://cdn.bootcss.com/babel-polyfill/7.0.0-alpha.9/polyfill.min.js"></script>-->
 10         <script type="text/babel">
 11             /*
 12              * Generator函数有多种理解角度。
 13              * 从语法上,首先可以理解成是一个状态机,封装了多个内部状态。
 14              * 
 15              * 执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,
 16              * 还是一个遍历器对象生成函数。返回的遍历器对象,可依次遍历Generator函数内部的每一个状态。
 17              * 
 18              * 形式上 Generator函数是一个普通函数,但是有两个特征。
 19              *  1.function关键字与函数名之间有一个*号。
 20              *  2.函数体内部使用yield语句,定义不同的内部状态(yield在英文里是产出的意思)。
 21              */
 22             
 23             function* helloWorldGenerator(){
 24                 yield "hello";
 25                 yield "world";
 26                 return "ending";
 27             }
 28             var hw=helloWorldGenerator();
 29             //上面定义了一个Generator函数,有三个状态。调用后,并不执行,返回的也不是函数的运行结果
 30             // 而是一个指向内部状态的指针,返回的是遍历器对象。
 31             //必须调用next方法,使指针移向下一个状态。
 32             console.log(hw.next());
 33             console.log(hw.next());
 34             console.log(hw.next());
 35             /*
 36              * 每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield或
 37              * return语句为止。换言之,Generator是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行。
 38              */
 39             
 40             /*
 41              * 遍历器对象的next方法的运行逻辑:
 42              *   1.遇到yield语句,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
 43              *   2.下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。
 44              *   3.如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为
 45              *      返回对象的value属性值。
 46              *   4.如果该函数没有return语句,则返回的对象的value属性值为undefined。
 47              * 
 48              * 要注意:yield语句后面的表达式,只有当调用next方法,内部指针指向该语句时才会执行,因此等于为js提供了手动的
 49              * 惰性求值的语法功能。
 50              */
 51             
 52             /*
 53              * Generator函数可以不用yield语句,这时就变成了一个单纯的暂缓执行函数。
 54              */
 55             function* f(){
 56                 console.log("执行了");
 57             }
 58             var generator = f();
 59             
 60             setTimeout(()=>{generator.next()},2000)
 61             
 62             //普通函数不能用yield语句,会报错。多用在有些方法的参数是普通函数。
 63             //yield语句用在一个表达式中,必须放在圆括号里面。
 64             
 65         //    console.log("hello" + (yield)) //报错Unexpected strict mode reserved word
 66             //yield语句用作函数参数或赋值表达式的右边,可以不加括号。
 67             
 68             /*
 69              * 与iterator的关系
 70              * 之前说过,任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数
 71              * 会返回该对象的一个遍历器对象。
 72              * 由于Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的Symbol.iterator属性,
 73              * 从而使该对象具有Iterator接口。
 74              */
 75             var myIterator = {};
 76             myIterator[Symbol.iterator] = function* (){
 77                 yield 1;
 78                 yield 2;
 79                 yield 3;
 80             }
 81             console.log([...myIterator])  //[1,2,3]
 82             
 83             //Generator函数执行后,返回一个遍历器对象。该对象本身也有一个Symbol.iterator 属性,执行后返回自身。
 84             function* gen(){
 85                 //somecode
 86             }
 87             var g = gen();
 88             
 89             console.log(g[Symbol.iterator]() === g)  //true  执行后返回自身
 90             
 91             /*
 92              * next方法的参数
 93              * yield本身没有返回值,或者说返回值是undefined。
 94              * next方法可以带一个参数,该参数就会被当做上一个yield语句的返回值。
 95              */
 96             
 97             function* para(){
 98                 for(let i=0;true;i++){
 99                     var reset = yield i;
100                     if(reset){
101                         i=-1;
102                     }
103                 }
104             };
105             var pa =para();
106             console.log(pa.next());
107             console.log(pa.next());
108             console.log(pa.next(true));
109             console.log(pa.next());
110             console.log(pa.next());
111             /*
112              * 这个参数有很重要的语法意义,Generator函数从暂停到恢复运行,它的上下文状态
113              * 是不变的。通过next的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值。
114              * 也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值。从而调整函数行为。
115              */
116             
117             
118             function* foo(x){
119                 var y = 2*(yield (x+1));
120                 var z = yield (y / 3);
121                 return (x+y+z);
122             }
123             var m = foo(5);
124 //            console.log(m.next());
125 //            console.log(m.next());
126 //            console.log(m.next());
127             /*  结果
128              * Object {value: 6, done: false}
129                Object {value: NaN, done: false}
130                Object {value: NaN, done: true}
131                
132                第二次运行的时候,next不带参数,导致y的值等于2*undefined(即NaN),除以3以后还是NaN。
133                第三次运行的时候不带参数,z等于undefined,返回对象的value属性等于5+NaN+undefined  即NaN。
134               
135               加上参数的运行结果完全不同:
136             * */
137             
138             console.log(m.next());
139             console.log(m.next(12));
140             console.log(m.next(13));
141             /*
142              * Object {value: 6, done: false}
143                Object {value: 8, done: false}
144                Object {value: 42, done: true}
145              */
146             
147             /*
148              * 注意由于next方法的参数表示的是上一个yield语句的返回值,所以第一次使用next方法不能带参数。v8引擎
149              * 直接忽略第一次使用next方法的参数。
150              * 
151              * 如果想要第一次调用next时,就能够输入值,可以在Generator函数外面再包一层:
152              */
153             
154             function wrapper(generatorFunction){
155                 return function(...args){
156                     let generatorObj = generatorFunction(...args);
157                     generatorObj.next();
158                     return generatorObj;
159                 }
160             }
161             const wrapped = wrapper(function*(){
162                 console.log(`First input:${yield}`);
163                 return 'Done';
164             })
165             
166             wrapped().next("hello!");
167             
168             //再看一个通过next方法,向Generator函数内部输入值的例子:
169             function* dataConsumer(){
170                 console.log("Strated!");
171                 console.log(`1.${yield}`)
172                 console.log(`2.${yield}`)
173             }
174             let dataObj = dataConsumer();
175             dataObj.next();   //strarted
176             dataObj.next("a");  //a
177             dataObj.next("b");   //b
178             
179             //for...of循环可以自动遍历Generator函数生成的Iterator对象,且不再需要next方法。
180             function*foo2(){
181                 yield 1;
182                 yield 2;
183                 yield 3;
184                 yield 4;
185                 return 5;
186             }
187             for(let v of foo2()){
188                 console.log(v);
189             }   //1 2 3 4
190             //z这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象。
191             //所以上面代码的return 语句返回的5,并不包括在for...of之中。
192             //下面是Generator函数结合for...of循环,实现斐波那契数列的例子:
193             function *fibonacci(){
194                 let [prev,curr] = [0,1];
195                 for(;;){
196                     [prev,curr] = [curr,prev + curr];
197                     yield curr;
198                 }
199             }
200             
201             for(let n of fibonacci()){
202                 if(n>1000) break;
203                 console.log(n);
204             }
205             //利用for...of循环,可以写出遍历任意对象的方法。原生js对象没有遍历接口,无法使用for...of,通过Generator
206             //加上这个接口就可以了。
207             
208             function *objectEntries(obj){
209                 let propKeys = Reflect.ownKeys(obj);
210                 
211                 for(let propKey of propKeys){
212                     yield [propKey,obj[propKey]];
213                 }
214             }
215             let jane = {first:"jane",last:"doe"}
216             for(let [key ,value] of objectEntries(jane)){
217                 console.log(`${key},${value}`);
218             }   //first,jane      last,doe
219             
220             //加上遍历器接口的另一种写法,将Generator函数加到对象的Symbol.iterator属性上面。
221             
222             function* objectEntries2(){
223                 
224                 let propKeys = Object.keys(this);
225                 
226                 for(let propKey of propKeys){
227                     yield [propKey,this[propKey]];
228                 }
229             }
230             let jane2 = {first:"jane2",last:"doe2"};
231             jane2[Symbol.iterator] = objectEntries2;
232             
233             for(let [key ,value] of jane2){
234                 console.log(`${key},${value}`);
235             } 
236             
237             //除了for...of以外,扩展运算符,结构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,
238             //他们都可以将Generator返回的Iterator对象,作为参数。
239             
240             function* numbers(){
241                 yield 1;
242                 yield 2;
243                 return 3;
244                 yield 4;
245             }
246             //扩展运算符
247             console.log([...numbers()])   //[1,2]
248             //Array.from方法
249             console.log(Array.from(numbers()));  //  [1,2]
250             //解构赋值
251             let [x,y] = numbers();
252             console.log(x,y);  //1 2
253             //for...of循环
254             for(let m of numbers()){
255                 console.log(m);  //1  2
256             }
257             
258             
259             //Generator.prototype.throw()
260             //Generator函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。
261             var g = function*(){
262                 try{
263                     yield;
264                 }catch(e){
265                     console.log("内部捕获",e);
266                 }
267             }
268             var i = g();
269             i.next();
270             try{
271                 i.throw("a");
272                 i.throw("b");
273             }catch(e){
274                 console.log("外部捕获",e);
275             }
276             
277             //上面代码中,遍历器对象i连续抛出两个错误。第一个被Generator函数体内的catch语句捕获。i第二次抛出错误,
278             //由于Generator函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误抛出了函数体,被函数体外的
279             //catch捕获。
280             
281             //throw方法可以接受一个参数,该参数会被catch语句接收,建议抛出Error实例。
282             //注意区分遍历器的throw和全局命令throw。全局的是无法被遍历器对象捕获的。
283             //throw方法被捕获以后会附带执行下一条yield语句。也就是说会执行一次next方法。
284             //Generator函数体外抛出的错误,可以在函数体内捕获,反过来,函数体内抛出的错误,也可以在函数体外的catch捕获。
285             
286             function* foo3(){
287                 var x = yield 1;
288                 var y = x.toUpperCase();
289                 yield y;
290             }
291             var it = foo3();
292             console.log(it.next());
293             try{
294                 it.next(45);
295             }catch(e){
296                 console.log(e);
297             }
298             
299             //一旦Generator执行过程中抛出错误,且没有被内部捕获,就不会再执行下去,如果此后还调用next方法,将返回一个value
300             //属性等于undefined,done属性等于true的对象,即js引擎认为这个Generator已经运行结束了。
301             
302             
303             //Generator.prototype.return 
304             //Generator函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历Generator函数。
305             function* foo3(){
306                 yield 1;
307                 yield 2;
308                 yield 3;    
309             }
310             var hu = foo3();
311             console.log(hu.next());  // 1 
312             console.log(hu.return("haha"));   //haha 
313             console.log(hu.next());   //undefined
314             
315             //如果return方法调用时,不提供参数,则返回值的value属性为undefined。
316             //如果Generator函数内部有try...finally代码,return会推迟到finally代码块执行完再执行。
317             
318             function * numbers2(){
319                 yield 1;
320                 try{
321                     yield 2;
322                     yield 3;
323                 }finally{
324                     yield 4;
325                     yield 5;
326                 }
327                 yield 7;
328             }
329             var g = numbers2();
330             console.log(g.next());   //1
331             console.log(g.next());   //2
332             console.log(g.return(7));   //4  开始执行finally
333             console.log(g.next());     //5
334             console.log(g.next());      //7  执行完finally  执行return 结束
335             console.log(g.next());   //已经结束。 undefined
336             
337             //yield*语句
338             //如果在Generator函数内部,调用另一个Generator函数,默认情况下是没有效果的。
339             //yield*语句,用来在一个Generator函数里面执行另一个Generator函数。
340             function*bar(){
341                 yield "a";
342                 yield "b";
343             }
344             function*baz(){
345                 yield "m";
346                 yield* bar();
347                 yield "n";
348             }
349             for(let v of baz()){
350                 console.log(v);   // m a b n
351             }
352             /*
353              * yield*后面的Generator函数(没有return语句时),等同于在Generator函数内部部署一个for...of循环。
354              * 如果有return,需要用var value = yield* iterator的形式获取return的值。
355              * 
356              * 如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。
357              * yield后面如果不加*号,返回的是整个数组,加上*号表示返回的数组的遍历器对象。
358              */
359             
360             //下面是使用yield*语句遍历完全二叉树
361             {
362                 //二叉树构造函数
363                 function Tree(left,label,right){
364                     this.left = left;
365                     this.right = right;
366                     this.label = label;
367                 }
368                 //中序遍历
369                 function* inorder(t){
370                     if(t){
371                         yield* inorder(t.left);
372                         yield t.label;
373                         yield* inorder(t.right);
374                     }
375                 }
376                 //生成二叉树
377                 function make(array){
378                     if(array.length == 1){
379                         return new Tree(null,array[0],null);
380                     }
381                     return new Tree(make(array[0]),array[1],make(array[2]));
382                 }
383                 let tree = make([[["a"],"b",["c"]],"d",[["e"],"f",["g"]]]);
384                 //遍历二叉树
385                 var result = [];
386                 for(let node of inorder(tree)){
387                     result.push(node);
388                 }
389                 
390                 console.log(result);
391                 //["a","b","c","d","e","f","g"]
392             }
393             
394             
395             //作为对象属性的Generator函数,可以简写成下面的形式
396             {
397                 let obj = {
398                     *myGenerator(){
399                         //code
400                     }
401                 }
402             }
403             
404             //Generator函数的this
405             //Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例,也继承了Generator函数的
406             //prototype对象上的方法。
407             {
408                 function* g(){}
409                 g.prototype.hello = function(){
410                     console.log("hi!");
411                 }
412                 let obj = g();
413                 
414                 console.log(obj instanceof g);  //true
415                 obj.hello();    //hi
416             }
417             //上面代码表明,Generator函数g返回的遍历器obj,是g的实例,而且继承了g.prototype.
418             //但是如果把g当做普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象、
419             {
420                 function* g(){
421                     this.a = 11;
422                 }
423                 let obj = g();
424                 console.log(obj.a);  //undefined
425             }
426             // 上面代码中,Generator函数g在this对象上添加了一个属性a,但是obj对象拿不到这个属性。
427             
428             //有一个变通的方法,让Generator函数返回一个正常的对象示例,既可以用next方法,又可以正常获得this:
429             {
430                 function *F(){
431                     this.a = 1;
432                     yield this.b = 2;
433                     yield this.c = 3;
434                 }
435                 var obj = {};
436                 var f = F.call(obj);
437                 
438                 console.log(f.next());  //2
439                 console.log(f.next());   //3
440                 console.log(f.next());   //undefined
441                 
442                 console.log(obj.a);   //1
443                 console.log(obj.b);   //2
444                 console.log(obj.c);  //3
445             }
446             
447             //上面代码中,执行的是遍历器对象f,生成的对象实例是obj,让他们统一的一个办法是将obj换成F.prototype。
448             {
449                 function *F(){
450                     this.a = 1;
451                     yield this.b = 2;
452                     yield this.c = 3;
453                 }
454                 
455                 var f = F.call(F.prototype);
456                 
457                 console.log(f.next());  //2
458                 console.log(f.next());   //3
459                 console.log(f.next());   //undefined
460                 
461                 console.log(f.a);   //1
462                 console.log(f.b);   //2
463                 console.log(f.c);  //3
464             }
465             //再将F改成构造函数,就可以对它执行new命令了
466             {
467                 function *gen(){
468                     this.a = 1;
469                     yield this.b = 2;
470                     yield this.c = 3;
471                 }
472                 function F(){
473                     return gen.call(gen.prototype);
474                 }
475                 var f = new F();
476                 
477                 console.log(f.next());  //2
478                 console.log(f.next());   //3
479                 console.log(f.next());   //undefined
480                 
481                 console.log(f.a);   //1
482                 console.log(f.b);   //2
483                 console.log(f.c);  //3
484             }
485             
486             //含义
487             //1.Generator与状态机
488             //Generator是实现状态机的最佳结构。比如下面的clock函数就是一个状态机。
489             {
490                 var ticking = true;
491                 var clock = function(){
492                     if(ticking){
493                         console.log("Tick!");
494                     }else{
495                         console.log("Tock!")
496                     }
497                     ticking = !ticking;
498                 }
499                 clock();
500                 clock();
501             }
502             //上面代码的clock函数一共有两种状态(tick和tock),每运行一次,改变一次状态。这个函数如果用Generator实现,就是下面这样。
503             {
504                 var clock = function*(){
505                     while(true){
506                         console.log("Tick!")
507                         yield;
508                         console.log("Tock!")
509                         yield;
510                     }
511                 }
512                 
513                 var c = clock();
514                 c.next();
515                 c.next();
516             }
517             
518             //上面的Generator实现,比ES5的实现,少了外部变量,这样更安全(状态不会被非法改变),简洁。
519             //也更 符合函数式编程的思想。
520             
521             
522             //Generator可以暂停函数执行,返回任意表达式的值。这种特点使得Generator有多种应用场景。
523             /*
524              * 1.异步操作的同步化表达。
525              * Generator函数的暂停执行效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时,再往后执行。
526              * 这实际上等同于不需要写回调函数了。因为异步操作的后序操作可以放在yield语句下面,反正要等到调用next方法时才执行。
527              * 所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
528              * 用读取文本的例子: 
529             {
530                 
531                 function* numbers(){
532                     let file = new FileReader("./[es6]-02-块级作用域.html");
533                     try {
534                         while(!file.eof){
535                             yield parseInt(file.readLine(),10)
536                         }
537                     }finally {
538                         file.close();
539                     }
540                 }
541                 console.log([...numbers()]);
542             }
543             这断代码在浏览器不会执行,报错。没有readFile.
544             */
545             
546             /*
547              * 2.控制流管理
548              * 如果有一个多步操作非常耗时,采用回调函数,可能会写成:
549              * step1(function(value1){
550              *     step2(function(vlaue2){
551              *     step3(function(value3){
552              *       //Do something with value3
553              * })
554              * })
555              * })
556              * 
557              * 采用promise改写上面的代码:
558              * Promise.resolve(step1)
559              *    .then(step2)
560              *    .then(step3)
561              *    .then(function(value3){
562              *     // Do something with value3
563              * },function(error){
564              *     //Handle any error from step1 through step4
565              * })
566              * 
567              * 上面的代码已经把回调函数改写成了直线执行的形式,但是假如了大量的Promise语法。
568              * Generator函数可以进一步改善代码运行流程:
569              * 
570              * function * longRunningTask(value1){
571              *     try{
572              *     var value2 = yield step1(value1);
573              *     var value3 = yield step2(value2);
574              *     var value4 = yield step3(value3);
575              * //Do something with value3
576              * }catch(e){
577              *     //Handle any error from step1 through step4
578              * }
579              * }
580              * 
581              * 然后使用一个函数,按次序自动执行所有步骤。
582              * 
583              * scheduler(longRunningtask(initialValue));
584              * function scheduler(){
585              *      var taskObj = task.next(task.value);
586              *   if(!taskObj.done){
587              *         task.value = taskObj.value;
588              *         scheduler(task)
589              * }
590              * }
591              * 
592              * //注意上面这种操作,只适合同步操作,不能有异步操作,因为这里的代码一得到返回值,
593              * //就继续往下执行,没有判断异步操作何时完成。
594              */
595             
596             /*
597              * 3.部署Iterator接口
598              * 利用Generator函数可以在任意对象上部署Iterator接口。
599              */
600             
601             /*
602              * 4.作为数据结构
603              * Generator可以看做数据结构,更确切的是可以看做一个数组结构。因为Generator函数可以返回一系列的值,
604              * 这意味着它可以对任意表达式,提供类似数组的接口。
605              */
606         </script>
607     </head>
608     <body>
609     </body>
610 </html>
原文地址:https://www.cnblogs.com/chengyunshen/p/7191672.html