• 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>
  • 相关阅读:
    进程池和线程池、协程、TCP单线程实现并发
    GIL全局解释锁,死锁,信号量,event事件,线程queue,TCP服务端实现并发
    进程补充和线程的介绍
    进程的介绍和使用
    异常处理和UDP Socket套接字
    TCP Socket 套接字 和 粘包问题
    网络编程
    面向对象高级——反射和元类
    面向对象三大特性之——多态和一些内置函数
    面向对象-内置方法
  • 原文地址:https://www.cnblogs.com/chengyunshen/p/7191672.html
Copyright © 2020-2023  润新知