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>

  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
}

  生成多个Generator:

  利用生成器的异步特性, 实现协程的JS代码, 在每一个生成器内部实现不同功能, 协程

<script>
function* first() {
    yield* function*() {
        setInterval(function() {
            console.log("setTimeout---0");
        },1000)
    }();
}
function* second() {
    yield* function*() {
        setInterval(function() {
            console.log("setTimeout---1");
        },1000)
    }();
}
function* third() {
    yield* function*() {
        setInterval(function() {
            console.log("setTimeout---2");
        },1000)
    }();
}
function runs(...args) {
    for(let arg of args) {
        arg.next();
    }
};
runs(first(), second(), third());
</script>

  参考:

    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

时间: 2024-08-24 03:54:13

ES6新特性:Javascript中Generator(生成器)的相关文章

ES6新特性三: Generator(生成器)函数详解

本文实例讲述了ES6新特性三: Generator(生成器)函数.分享给大家供大家参考,具体如下: 1. 简介 ① 理解:可以把它理解成一个函数的内部状态的遍历器,每调用一次,函数的内部状态发生一次改变. ② 写法: function* f() {} ③ 作用:就是可以完全控制函数的内部状态的变化,依次遍历这些状态. ④ 运行过程:当调用Generator函数的时候,该函数并不执行,而是返回一个遍历器(可以理解成暂停执行).通过调用next()开始执行,遇到yield停止执行,返回一个value

JavaScript学习--Item24 ES6新特性概览

ES6新特性概览 本文基于lukehoban/es6features ,同时参考了大量博客资料,具体见文末引用. ES6(ECMAScript 6)是即将到来的新版本JavaScript语言的标准,代号harmony(和谐之意,显然没有跟上我国的步伐,我们已经进入中国梦版本了).上一次标准的制订还是2009年出台的ES5.目前ES6的标准化工作正在进行中,预计会在14年12月份放出正式敲定的版本.但大部分标准已经就绪,且各浏览器对ES6的支持也正在实现中.要查看ES6的支持情况请点此. 目前想要

javascript ES6 新特性之 扩展运算符 三个点 ...

对于 ES6 新特性中的 ... 可以简单的理解为下面一句话就可以了: 对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中. 作用类似于 Object.assign() 方法,我们先来看一下 Object.assign() 方法: Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象.它将返回目标对象.如下: const target = { a: 1, b: 2 }; const source = { b: 4, c: 5

ES6新特性

ES6新特性概览 箭头操作符 如果你会C#或者Java,你肯定知道lambda表达式,ES6中新增的箭头操作符=>便有异曲同工之妙.它简化了函数的书写.操作符左边为输入的参数,而右边则是进行的操作以及返回的值Inputs=>outputs. 我们知道在JS中回调是经常的事,而一般回调又以匿名函数的形式出现,每次都需要写一个function,甚是繁琐.当引入箭头操作符后可以方便地写回调了.请看下面的例子. var array = [1, 2, 3]; //传统写法 array.forEach(f

你不知道的JavaScript--Item24 ES6新特性概览

ES6新特性概览 本文基于lukehoban/es6features ,同时参考了大量博客资料,具体见文末引用. ES6(ECMAScript 6)是即将到来的新版本JavaScript语言的标准,代号harmony(和谐之意,显然没有跟上我国的步伐,我们已经进入中国梦版本了).上一次标准的制订还是2009年出台的ES5.目前ES6的标准化工作正在进行中,预计会在14年12月份放出正式敲定的版本.但大部分标准已经就绪,且各浏览器对ES6的支持也正在实现中.要查看ES6的支持情况请点此. 目前想要

ES6新特性概览

转自:http://www.cnblogs.com/Wayou/p/es6_new_features.html ES6学习可参考:http://es6.ruanyifeng.com/ 本文基于lukehoban/es6features ,同时参考了大量博客资料,具体见文末引用. ES6(ECMAScript 6)是即将到来的新版本JavaScript语言的标准,代号harmony(和谐之意,显然没有跟上我国的步伐,我们已经进入中国梦版本了).上一次标准的制订还是2009年出台的ES5.目前ES6

34.JS 开发者必须知道的十个 ES6 新特性

JS 开发者必须知道的十个 ES6 新特性 这是为忙碌的开发者准备的ES6中最棒的十个特性(无特定顺序): 默认参数 模版表达式 多行字符串 拆包表达式 改进的对象表达式 箭头函数 =&> Promise 块级作用域的let和const 类 模块化 注意:这个列表十分主观并且带有偏见,其他未上榜的特性并不是因为没有作用,我这么做只是单纯的希望将这份列表中的项目保持在十个. 首先,一个简单的JavaScript时间线,不了解历史的人也无法创造历史. 1995年:JavaScript以LiveS

前端入门21-JavaScript的ES6新特性

声明 本篇内容全部摘自阮一峰的:ECMAScript 6 入门 阮一峰的这本书,我个人觉得写得挺好的,不管是描述方面,还是例子,都讲得挺通俗易懂,每个新特性基本都还会跟 ES5 旧标准做比较,说明为什么会有这个新特性,这更于理解. 所以,后续不会再写个关于 ES6 系列的文章了,就在这篇里大概的列举一下,大体清楚都有哪些新特性就好了,以后需要用时再去翻一翻阮一峰的书. 正文-ES6新特性 ES6 新标准规范相比于 ES5 旧标准规范中,无非就三个方面的改动:新增.更新.废弃. 由于更新和废弃需要

ES6新特性:Proxy代理器

ES6新特性:Proxy: 要使用的话, 直接在浏览器中执行即可, node和babel目前还没有Proxy的polyfill;,要使用的话,直接在浏览器中运行就好了, 浏览器的兼容性为:chrome>39或者firefox>18: Proxy的基本使用: Proxy如其名, 它的作用是在对象和和对象的属性值之间设置一个代理,获取该对象的值或者设置该对象的值, 以及实例化等等多种操作, 都会被拦截住, 经过这一层我们可以统一处理,我们可以认为它就是“代理器” ; Proxy是一个构造函数, 使