生成器是ES6中新增的一种特殊的函数,通过“function*”来声明,函数体内通过“yield”来指明函数暂停点。生成器函数调用后是返回一个迭代器iterator,并且函数执行到第一个“yield”语句前面暂停,之后通过调用返回的迭代器的next()方法来执行yield语句。一个yield语句就是一个暂停点。
function* generator(){ yield 1; yield 2; yield 3; } var gen=generator(); while((tmp=gen.next()).done==false){ console.log(tmp.value); } //1 //2 //3
返回的迭代器iterator每次调用next会返回一个对象{value:"xxx",done:true/false},value是返回值,done表示是否达到迭代器的结尾。
上面的代码中,我们定义了一个简单的生成器,函数体内有三条yield断点语句。调用generator()返回一个迭代器gen,然后在while循环中调用gen的next方法执行yield语句直到碰到下一个yield断点,通过判断next返回对象的done是否为true来判断生成器是否执行结束,通过返回对象的value得到yield的返回值。这个值是yield语句返回的。yield有点“return”的作用,你可以把它看作是可以暂停函数的return语句。
next()方法中可以传入一个参数,这个参数会作为上一个yield语句的返回值的,如果不传参数,yield语句中生成器函数内的返回值是undefined。
function* withparam(x){ var y=yield x; yield y; } var wt=withparam(3); console.log(wt.next()); console.log(wt.next());
第二个next调用的时候的value是undefined的。现在我们给第二个next传入参数,
function* withparam(x){ var y=yield x; yield y; } var wt=withparam(3); console.log(wt.next()); console.log(wt.next(5));
第二个next传入了5,所以y被赋值为5。记住永远不要做往第一个next中传入参数的傻事,因为next传入的参数会作为上一个yield语句中生成器函数内部的返回值,而第一个next执行时,没有上一个yield语句,也就没有接收参数的地方。
基于上面next的传值特性,我写了一个累乘的函数,大家可以看一下:
function mymulti(){ function* multiple(){ var tmp=1; var result=1; while(true){ tmp=result*tmp; var result=yield tmp; } } var gen=multiple(); gen.next(); return { multi:function(x){ gen.next(x).value; return this; }, result:function(){ return gen.next(1).value; } } } var mt=mymulti(); mt.multi(2).multi(3).multi(5); console.log(mt.result());
这个函数利用了生成器的特性,以及next方法的传值特征,实现了2*3*5=30的累加乘法功能。因为第一次的next传值没有用途,所以在mymulti函数内部调用了一次gen.next(),之后外部调用的multi(2)会将2传递给result参数,result参数与保存的数值相乘,并利用变量作用域继续保存。
对于网上有说用生成器解决异步问题,因为没有时间自己去写一个后台访问去,所以就写了一段伪代码来解释用generator解决异步问题的方法。
使用generator调用ajax,可以像写同步代码一样使用。
function* tasks(){ var rst=yield myAjax("getData.html"); if(rst.code==0){ console.log("加载数据成功!"); renderPage(rst.data); } var comment=yield myAjax("getComment.html"); if(comment.code==0){ console.log("加载评论成功!"); renderComment(comment.data); } }
然后我们需要封装一个用来执行生成器方法的函数。
function runTask(tks,callback){ var gen=tks(); var result; function gonext(g,param){ result= g.next(param); var value=result.value; if(!result.done) { if (value instanceof Promise) { value.then(function (rst) { gonext(g, rst); }); } else { gonext(g, rst); } }else{ callback.call(window); } } gonext(gen); }
上面是一个runTask方法,用来执行生成器函数,不是完整代码,只是原理示意代码,这个函数内部调用生成器函数,得到迭代器,然后内部定义了一个递归调用的函数,这个函数里面执行迭代器的next方法, 并传递参数,对于next返回对象的value,判断是否是Promise异步链对象,是的话,这调用它的then方法,在then方法的回调中递归调用gonext方法,不是异步方法,就直接调用gonext递归,直到done为true时结束。这时候再调用runTask传递的回调函数。
你可以这样执行我们生成器里面的代码:
runTask(tasks,function(){ console.log("页面初始化完成了"); });
虽然还是利用了promise的特性,但至少我们的代码书写上更像同步代码了。
此外,生成器函数中还有一个“yield*”没有提到,它是可以在生成器里面嵌套生成器对象或者其他实现了迭代器接口的对象。
function* generator(){ yield 1; yield* ["a","b","c","d"]; yield 3; } var gen=generator(); while((tmp=gen.next()).done==false){ console.log(tmp.value); }
意思很明白,就跟函数内部调用子函数的效果差不多。