6-未来的函数:生成器

ES6前沿特性:生成器和promise

生成器

1. 生成器的概念和迭代器

用途:使用生成器来简化复杂循环,使用其能力还挂起和恢复循环,写出更简单、优雅的异步代码

概念:生成器函数(generator)能生成一组值的序列,但每个值的生成是基于每个yield显示请求新值,随后生成器要么响应一个新值,要么告诉我们它不会再生成新值。生成器每次生成一个新值后,都会更新为挂起状态,然后等待另一个值的请求到来后,生成器会从上次离开的位置重新执行。

    // 使用生成器生成值
    function * wea(){  // function后面添加 * 定义生成器函数
        yield 3;       // yield 显示的请求值
        yield 9;       // 注意生成器函数没有 return返回
    }
    //使用新的for of循环类型遍历新值
    for(let value of wea()){  // wea()调用生成器函数并不会执行该生成器,会返回迭代器对象
        console.log(value)
    }
    
    

    //使用迭代器对象控制生成器
    const weaObj = wea(); 
    const res1 = weaObj.next();
    console.log('res1:',res1); // {value: 3, done: false}
    
    const res2 = weaObj.next();
    console.log('res2:',res2); // {value: 9, done: false}
    
    const res3 = weaObj.next();
    console.log('res3:',res3); // {value: undefined, done: true}
    
//调用生成器函数返回迭代器对象,该对象暴露一个next方法,向生成器请求一个新值
//next函数调用后,生成器开始执行代码,碰到yield会生成一个中间结果(生成值序列中的一个),然后返回对象,包含生成值和是否结束。
//每当生成一个当前值后,生成器会非阻塞的挂起执行,等待下一次值请求的到来
//随后通过next的再次调用,再次向生成器请求一个值,该生成器会从挂起状态唤醒,中断的生成器从上次离开的位置继续执行代码,直到碰到yield生成中间结果,再次挂起。
//当next调用后,没有更多可执行代码了,返回结果对象,{value: undefined, done: true}



    //对迭代器进行迭代
    const weaObj1 = wea();
    let item;
    //判断迭代器next方法返回的对象的done属性,只要不为false,就一直循环遍历该迭代器的next
    //这也是for of的原理,for of是迭代的语法糖
    while(!(item = weaObj1.next()).done){ 
    console.log(item);
    }
    
    
    
    // 把执行权交给下一个生成器
    function * Generator(){  
        yield "a";      
        yield * newGenerator() ; // yield * 函数(),跳转到其他生成器函数的执行上
        yield "b";

    }
    function * newGenerator(){
        yield "newGenerator-1";
        yield "newGenerator-2";
    }
    
    //在forof循环生成器中,是每次调用迭代next方法,
    //不关心是哪一个生成器,遇到newGenerator()会重新寻址到newGenerator()方法上,
    //走完了它的yield会重新回到本生成器中继续走完。
    for(let value of Generator()){  
        console.log(value)
    }

    //迭代器的模样
    console.log(weaObj) 
    wea {<closed>}
     __proto__: Generator
     [[GeneratorLocation]]: VM138:1
     [[GeneratorStatus]]: "closed"
     [[GeneratorFunction]]: ƒ * wea()
     [[GeneratorReceiver]]: Window

2.生成器的使用

    // 遍历dom节点
    // 方法一:回调函数
    function traverseDom(element,callback){

        callback(element); // 用回调函数处理当前节点

        element = element.firstElementChild; //对象下的第一个子元素
        console.log('子:',element)

         // 遍历每个子树
        while(element){
            // console.log("进入while")
            traverseDom(element,callback) // 子元素作为副元素递归调用
            element = element.nextElementSibling; // 同级的兄弟元素,可有空格
            console.log('兄弟',element)
        }
    }

    const subtree = document.getElementById('subtree');

    // 通过traverseDom方法,从根节点开始遍历
    // 函数调用。传了两个参数:一个形参,一个函数;
    traverseDom(subtree,function(element){
        console.log('父:',element);
    })
    // 方法二:
    // 获取DOM根节点
    var root = document.getElementById('subtree');
    console.log(root); 

    // 获取根节点的孩子
    function forDOM(root1) {
        var children = root1.children;
        // 遍历孩子
           for (var i = 0; i < children.length; i++) {
            var child = children[i];
            console.log(child);
            // 如果child下面有子节点,那么就继续遍历
            child.children && forDOM(child); //等价于下面的判断
            // if(child.children){
            //  forDOM(child);
            // }

        }
    }
    forDOM(root);
        
        
    // 获取页面中的根节点--根标签
    var root1 = document.getElementById('subtree');
    log(root1);
    //函数遍历DOM树
    //根据根节点,调用fn的函数,显示的是根节点的名字
    function forDom(root1) {
        //调用log,显示的是节点的名字
        //获取根节点中所有的子节点
        var children = root1.children;
        //调用遍历所有子节点的函数
        forChildren(children);
    }
    //给我所有的子节点,我把这个子节点中的所有的子节点显示出来
    function forChildren(children) {
        //遍历所有的子节点
        for(var i = 0;i < children.length;i++){
          //每个子节点
          var child = children[i];
          //显示每个子节点的名字
          log(child);
          //判断child下面有没有子节点,如果还有子节点,那么就继续的遍历
          child.children && forDom(child);
        }
    }
    //函数调用,传入根节点
    forDom(root1);
    
    // log方法
    function log(node) {
        console.log("节点的名字:",node);
    }

// 方法三:迭代器方式
function * DomTraversal(element) {
        yield element;
        element = element.firstElementChild;
        while ((element)) {
           //用yield*将迭代控制转移到另一个DomTraversal生成器实例上
           yield* DomTraversal(element);
           element = element.nextElementSibling;
        }
    }
    
    const subTree = document.getElementById("subtree");
    for(let element of DomTraversal(subTree)){
       console.log(element);
    }

3.与生成器交互,双方通信

function * Method(action){

        const value = yield("hello" + "-" + action); 
        
        console.log('value',value) //"zhaosi"。      
        const newvalue = yield(  value + "-" + action); 
        
        console.log('newvalue',newvalue)    
        yield(  newvalue+"-"+action);
    
    }

    const ninObj = Method("anna"); // 调用生成器函数返回一个迭代对象,此时生成器函数不执行

    //迭代对象暴露next方法,调用该方法,生成器函数开始执行,执行到遇到第一个yield,生成器挂起。
    //此时yield如果是表达式,该表达式还没生成返回值就被挂起了。
    const result1 = ninObj.next(); 
    console.log('result1',result1);// {value: "hello-anna", done: false}

    const result2 = ninObj.next('zhaosi'); //next的传参,会传给执行到上一个yield的表达式返回值
    console.log('result2',result2); 

    const result3 = ninObj.next("change");
    console.log('result3',result3);


// 第一次next,执行到yield表达式时,生成器挂起,迭代对象返回{value:"hello-anna",done:false}对象,但是此时yield表达式没生成返回值或者已经为unfined,无所谓,最终yield表达式的返回值只能在第二次next才能打印获取到;
// 第二次next(),生成器从上次挂起的地方,第一次yield表达式拿到yield返回值赋给变量value,
// 第二次next('zhaosi'),生成器从上次挂起的地方,第一次yield表达式拿到yield返回值赋给变量value,yield返回值被替换为next('zhaosi')的传参 zhaosi,所以value的值变成了zhaosi;本次执行到新的yield,迭代器返回{value:'zhaosi-anna',done:false}
// 第三次next,执行到第三个yield,如果有传参,会传入到上次到yield表达式返回值里
    
// 生成器抛出异常 :改善异步服务器端的通信
function * nin(){
        try{
            yield "hello"
            console.log('hello')
        }
        catch(e){
            if(e==='catchthis'){
                console.log(e)
            }

        }
    }
    const ninObj = nin() //生成迭代器
    const res1 = ninObj.next() //执行yield,返回value done的对象
    console.log('res1',res1)  //{value: "hello", done: false}
    ninObj.throw("catchthis") //迭代器对象还有一个throw方法,抛出异常

4.探索生成器内部构成

生成器的四种状态
- 挂起开始:调用生成器,创建了生成器,不执行代码,返回迭代器,暴露next方法
- 执行:next后
- 挂起让渡:next后,执行过程中遇到了一个yield表达式,会创建包含返回值的新对象,随后挂起执行。生成器在这个状态暂停并等待继续执行
- 完成:在生成器执行期间,代码执行到return或者全部执行完。进入的状态。
生成器和函数的不同

- 标准函数仅仅会被重复调用,每次调用都会创建一个新的执行上下文
- 生成器的执行环境上下文会暂时挂起来并在将来恢复

    - 控制流进入生成器时,当前创建一个新的函数上下文,并将该上下文入栈。而生成器比较特殊,不会执行任何代码,生成一个迭代器并返回。  
      迭代器是用来控制生成器的执行的,所以迭代器中保存着一个在他创建位置出的执行上下文。
      
    - 当程序从生成器中执行完毕后,一般函数的执行环境上下文会从栈中弹出,并完成销毁。生成器函数会弹出,  
      但是因为迭代器对象还保存着引用,所以不会被销毁,类似闭包,只要函数还在,环境和变量就都存在。  
      从另一个角度看,生成器迭代器保持了一个对当前执行环境对引用,保证只要迭代器还需要他的时候它都在。
      
    - `const res=nin.next()` 会重新激活对应的执行上下文。就是nin上下文,并把该上下文放入栈的顶部,从它上次离开的地方继续执行。  
      在产生一个值后,生成器的执行环境上下文就会从栈中弹出,
      但因为有迭代器引用在所以不会销毁,生成器挂起执行,即生成器的执行环境上下文是一直保存的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容