ES6的很多特性都跟Generator扯上关系,而且实际用处比较广, 包含了任何需要异步的模块, 比如ajax, filesystem, 或者数组对象遍历等都可以用到;
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()); // 1console.log(it.next()); // 2console.log(it.next()); //undefined
yield
Generator函数返回的Iterator运行的过程中,如果碰到了yield, 就会把yield后面的值返回, 此时函数相当于停止了, 下次再执行next()方法的时候, 函数又会从上次退出去的地方重新开始执行;
如果把yield和return一起使用的话, 那么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 2 和yield 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='/images/loading.gif' data-original="http://www.cnblogs.com///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 "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
原标题:ES6新特性:Javascript中Generator(生成器)
关键词:JavaScript