• ES6新特性:Javascript中Generator(生成器)


      ES6的很多特性都跟Generator扯上关系,而且实际用处比较广, 包含了任何需要异步的模块, 比如ajaxfilesystem, 或者数组对象遍历等都可以用到;

      Generator的使用:

      Generator函数和普通的函数区别有两个, 1:function和函数名之间有一个*号, 2:函数体内部使用了yield表达式;比如这样:

    function* gen() {
        yield "1";
        yield "2"
    }

       这个玩意儿如果运行的话,会返回一个Iterator实例, 然后再执行Iterator实例的next()方法, 那么这个函数才开始真正运行, 并把yield后面的值包装成固定对象并返回,直到运行到函数结尾, 最后再返回undefined; 

    "use strict";
    function* fibonacci() {
        yield 1;
        yield 2;
    }
    
    var it = fibonacci();
    console.log(it);          // "Generator {  }"
    console.log(it.next());   // 1
    console.log(it.next());   // 2
    console.log(it.next()); //undefined

      

      yield

      Generator函数返回的Iterator运行的过程中,如果碰到了yield, 就会把yield后面的值返回, 此时函数相当于停止了, 下次再执行next()方法的时候, 函数又会从上次退出去的地方重新开始执行;

      如果把yieldreturn一起使用的话, 那么return的值也会作为最后的返回值, 如果return语句后面还有yield, 那么这些yield不生效:

    function* gen() {
        yield 0;
        yield 1;
        return 2;
        yield 3;
    };
    let g = gen();
    console.log(g.next(),g.next(),g.next(),g.next());
    //输出:{ value: 0, done: false } { value: 1, done: false } { value: 2, done: true } { value: undefined, done: true }

      我们也不能在非Generator函数中使用yield,比如:

    <script>
    var arr = [1, [[2, 3], 4], [5, 6]];
    var flat = function* (a) {
        a.forEach(function (item) {
            if (typeof item !== 'number') {
                yield* flat(item);
            } else {
                yield item;
            }
        })
    };
    
    for (var f of flat(arr)){
        console.log(f);
    }
    </script>

      上面的demo因为callback是一个普通函数, 所以编译的时候直接抛出错误提示, 我们需要改成在Generator的函数体中:

    <script>
    var arr = [1, [[2, 3], 4], [5, 6]];
    var flat = function* (a) {
        var length = a.length;
        for (var i = 0; i < length; i++) {
            var item = a[i];
            if (typeof item !== 'number') {
                yield* flat(item);
            } else {
                yield item;
            }
        }
    };
    for (var f of flat(arr)) {
        console.log(f);
    }
    </script>

      或者有个更奇怪的方法,我们把数组的forEach改成Generator函数:

    <script>
    var arr = [1, [[2, 3], 4], [5, 6]];
    Array.prototype.forEach = function* (callback) {
        for(var i=0; i<this.length; i++) {
            yield* callback(this[i],i ,this[i]);
        }
    }
    var flat = function* (a) {
        yield* a.forEach(function* (item) {
            if (typeof item !== 'number') {
                yield* flat(item);
            } else {
                yield item;
            }
        })
    };
    
    for (var f of flat(arr)){
        console.log(f);
    }
    </script>

      而且Iterator的return的值不会被for...of循环到 , 也不会被扩展符遍历到, 以下Demo的return 2yield 3完全不生效了, 这个是要注意的;

    function* gen() {
        yield 0;
        yield 1;
        return 2;
        yield 3;
    };
    let g = gen();
    console.log([...g]); //输出:[ 0, 1 ]
    for(let foo of g) {
        console.log( foo ); //输出 0, 1
    }

      yield*

      yield*这种语句让我们可以在Generator函数里面再套一个Generator, 当然你要在一个Generator里面调用另外的Generator需要使用: yield* 函数() 这种语法, 都是套路啊:

    function* foo() {
        yield 0;
        yield 1;
    }
    function* bar() {
        yield 'x';
        yield* foo();
        yield 'y';
    }
    for (let v of bar()){
        console.log(v);
    };

      next()方法

      Generator函数返回的Iterator执行next()方法以后, 返回值的结构为:

    {
        value : "value", //value为返回的值
        done : false //done的值为一个布尔值, 如果Interator未遍历完毕, 他会返回false, 否则返回true;
    }

      所以我们可以模拟一个Generator生成器, 利用闭包保存变量, 每一次执行next()方法, 都模拟生成一个{value:value,done:false}的键值对:

    function gen(array){
        var nextIndex = 0;
        return {
            next: function(){
                return nextIndex < array.length ?
                {value: array[nextIndex++], done: false} :
                {value: undefined, done: true};
            }
        };
    };
    
    var it = gen(["arr0", "arr1", "arr2", "arr3"]);
    console.log( it.next() );
    console.log( it.next() );
    console.log( it.next() );
    console.log( it.next() );
    console.log( it.next() ); 

      再浪一点的话,我们也可以模拟一个对象的Iterator, 因为本身对象是没有Iterator的, 我们为对象添加[Symbol.iterator]方法:

    <script>
    var itObj = {
        0:"00",
        1:"11",
        2:"22",
        3:"33",
        length : 4,
        [Symbol.iterator]() {
            const _this = this;
            let index = 0;
            return {
                next() {
                    if(index< _this.length) {
                        return {
                            value : _this[index++],
                            done : false
                        }
                    }else{
                        return {
                            value : undefined,
                            done : true
                        }
                    }
                }
            }
        }
    };
    console.log([...itObj]);
    </script>
    View Code

      next()方法的参数

      如果给next方法传参数, 那么这个参数将会作为上一次yield语句的返回值 ,这个特性在异步处理中是非常重要的, 因为在执行异步代码以后, 有时候需要上一个异步的结果, 作为下次异步的参数, 如此循环::

    <script>
    function* foo(x) {
        var y = 2 * (yield (x + 1));
        var z = yield (y / 3);
        return (x + y + z);
    }
    
    var a = foo(5);
    a.next() // Object{value:6, done:false}
    a.next() // Object{value:NaN, done:false}
    a.next() // Object{value:NaN, done:true}
    
    var b = foo(5);
    b.next() // { value:6, done:false }
    b.next(12) // { value:8, done:false }
    b.next(13) // { value:42, done:true }
    </script>

      上面的demo看懂了, next()方法的参数怎么使用也就懂了;

      throw方法()

      如果执行Generator生成器的throw()方法, 如果在Iterator执行到的yield语句写在try{}语句块中, 那么这个错误会被内部的try{}catch(){}捕获 :

    <script>
    var
    g = function* () { try { yield; } catch (e) { console.log('内部捕获0', e); } }; var i = g(); i.next(); //让代码执行到yield处; try { i.throw('a'); } catch (e) { console.log('外部捕获', e); }
    </script>

      如果Interator执行到的yield没有写在try{}语句块中, 那么这个错误会被外部的try{}catch(){}语句块捕获;

    
    
    <script>
    var g = function* () {
        while(true) {
            try {
                yield;
            } catch (e) {
                console.log('内部捕获', e);
            }
        }
    };
    
    var i = g();
    i.next();
    
    try {
        i.throw('a');
        i.throw('b');
    } catch (e) {
        console.log('外部捕获', e);
    }
    </script>

      return()方法:

      如果执行Iterator的return()方法, 那么这个迭代器的返回会被强制设置为迭代完毕, 执行return()方法的参数就是这个Iterator的返回值,此时done的状态也为true:

    <script>
    function* gen() {
        yield 0;
        yield 1;
        yield 2;
        yield 3;
    };
    let g = gen();
    console.log(g.return("heheda")); //输出:{ value: 'heheda', done: true }
    </script.

      Generator中的this和他的原型

      Generator中的this就是谁调用它,那么this就是谁, 我们利用Reflect.apply可以改变Generator的上下文:

    function* gen() {
        console.log(this);
        yield 0;
    };
    console.log(gen().next());
    console.log(Reflect.apply(gen,"heheda").next());

      Generator生成的Iterator,不但继承了Iterator的原型, 也继承了Generator的原型:

    <script>
    function* gen() {
        console.log(this);
        yield 0;
    };
    gen.prototype.foo = ()=> {
        console.log("foo");
    }
    let g = gen();
    console.log(Reflect.getPrototypeOf(g) === gen.prototype); //输出:true
    </script>

      所以如果要让生成器继承方法, 我们可以这样, 感觉好酷, 但是Generator内部的this是指向原型的, 也就是说已经把原型污染了:

    <script>
    function* gen() {
        this.bar = "bar";
        yield 0;
    };
    gen.prototype.foo = ()=> {
        console.log("foo");
    }
    let g = Reflect.apply(gen, gen.prototype,[]);
    console.log(g.next());  //输出:Object {value: 0, done: false}
    console.log(g.bar); //输出:bar
    </script>

      实际使用:

      ajax的异步处理, 利用生成器的特性,不但可以用于ajax的异步处理, 也能够用于浏览器的文件系统filesystem的异步:

    <html>
    <head>
        <meta charset="utf-8">
        <script src="//cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js"></script>
    </head>
    <body>
        <script>
            "use strict";
            function* main() {
                var result = yield request("http://www.filltext.com?rows=10&f={firstName}");
                console.log(result);
                //do 别的ajax请求;
            }
    
            function request(url) {
                var r = new XMLHttpRequest();
                r.open("GET", url, true);
                r.onreadystatechange = function () {
                    if (r.readyState != 4 || r.status != 200) return;
                    var data = JSON.parse(r.responseText);
                    //数据成功返回以后, 代码就能够继续往下走了;
                    it.next(data);
                };
                r.send();
            }
    
            var it = main();
            it.next();
            console.log("执行到这儿啦");
        </script>
    </body>
    </html>

      以上代码中的console.log("执行到这儿啦");先被执行了, 然后才出现了ajax的返回结果, 也就说明了Generator函数是异步的了;   

      

      利用Generator函数,可以在任意对象上部署iterator接口:

    function* iterEntries(obj) {
        let keys = Object.keys(obj);
        for (let i=0; i < keys.length; i++) {
            let key = keys[i];
            yield [key, obj[key]];
        }
    }
    
    let myObj = { foo: 3, bar: 7 };
    
    for (let [key, value] of iterEntries(myObj)) {
        console.log(key, value); //输出:foo 3 , bar 7
    }

      参考:

        https://davidwalsh.name/es6-generators
        https://davidwalsh.name/es6-generators-dive
        https://davidwalsh.name/async-generators
        https://davidwalsh.name/concurrent-generators
        http://www.2ality.com/2015/03/es6-generators.html
        http://es6.ruanyifeng.com/#docs/generator
        https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator

    作者: NONO
    出处:http://www.cnblogs.com/diligenceday/
    QQ:287101329
    微信:18101055830 

  • 相关阅读:
    【面霸2】
    【面霸1】php知识点
    【技术宅11】php入门运算
    【技术宅10】顺序二分查找算法
    【技术宅9】遍历一个文件夹下的所有文件和子文件夹
    【技术宅7】一个或多个虚拟主机配置方法
    【技术宅6】把一个无限级分类循环成tree结构
    【技术宅5】抓去网页数据的3种方法
    【技术宅4】如何把M个苹果平均分给N个小朋友
    【技术宅3】截取文件和url扩展名的N种方法
  • 原文地址:https://www.cnblogs.com/diligenceday/p/5488037.html
Copyright © 2020-2023  润新知