首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

ES6系列Generator生成器

一.简介

1. 基本概念

Generator函数是ES6提供的一种异步编程解决方案。

Generator函数是一个状态机,封装了多个内部状态。执行函数会返回一个遍历器对象,也就是Generator是一个Iterator生成器

Generator和其他函数的形式上的区别:1.function关键字和函数名之间有一个星号*; 2.函数体内部使用yield表达式定义不同的内部状态(yield就是产出的意思)

在调用方法上,Generator函数被调用后,函数并不执行,而是返回一个指向内部状态的指针对象(遍历器对象)

必须调用遍历器对象的next方法,使得指针指向下一状态,也就是每次调用next方法,内部指针就从函数头部或者上次停下来的地方开始执行

直到遇到yiled表达式或者return语句。也就是Generator函数是分段执行的,yield表达式是暂停执行的,next方法是恢复执行

看一个例子:

function*?gene(){

yield?1;

yield?2;

yield?3;

}

var?res=gene();

console.log(res.next());//{value:?1,?done:?false}

console.log(res.next());//{value:?2,?done:?false}

console.log(res.next());//{value:?3,?done:?false}

console.log(res.next());//{value:?undefined,?done:?true}

从上面可以看到执行generator函数返回的是一个遍历器对象

done属性表示是否遍历结束,value表示当前状态值,使用next方法返回的是一个对象,对象形式为"{value:'',done:布尔值}"

当生成器函数还有yield表达式未执行时,返回的value存在值不为undefined,done为false

当执行的是最后一个yield表达式,后续的next方法返回的done为true,value为undefined

星号的位置

function?*?gene(){}

function*?gene(){}

//?*gene不会被识别为函数名,因为变量开头必须是数字/字母/$/_

function?*gene(){}

function*gene(){}

//?错误写法!(此时*解析为函数名了)

function?gene*(){};//?Unexpected?token?'*'

2.yield表达式

generator函数返回的是一个遍历器对象,只有调用next方法才会遍历下一个内部状态

所以yield表达式就是一种标识,标识返回对应值,然后把状态暂停到此处

注意yiled后面的表达式必须在执行对应的next方法后才会执行,否则不会执行该表达式,也就是惰性求值

Generator函数可以不用yield表达式,此时就是一个单纯的暂缓执行函数

function*?f(){

console.log("没有yield表达式的generator函数就是一个暂缓执行函数")

}

var?res=f();//?打印上面的语句

console.log(res.next());//{value:?undefined,?done:?true}

yield表达式只能用于生成器函数

//?1.?普通函数

//?编译阶段就会报错,Uncaught?SyntaxError:?Unexpected?number

function?f(){

yield?1;

}

//?2.嵌套函数

//?编译时报错?Unexpected?identifier

function*?foo(arr){

arr.forEach(function(item){

yield?item;

})

}

//?3.立即执行函数

//?报错,SyntaxError:?Unexpected?number

(function(){

yield?1;

})()

//?4.?可以用于for循环中

function*?one(arr){

for(var?i=0;i

yield?arr[i]

}

}

var?res=one([5,6])

console.log(res.next());//{value:?5,?done:?false}

console.log(res.next());//{value:?6,?done:?false}

console.log(res.next());//{value:?undefined,?done:?true}

yield表达式在其他语句中

function*?f(){

//?1.?没有+号,此时无反应

console.log(yield?2);//?无反应

//?2.?偶加号,此时报错

//?console.log("2:"+yield);//?报错:SyntaxError:?Unexpected?identifier

//?console.log("2:"+yield?2);//?报错:SyntaxError:?Unexpected?identifier

//?3.如果在一个表示式里面,那么可以把yield语句放到圆括号里面

//?console.log("3:"+(yield?3));

console.log("3:"+(yield?));

}

var?res=f()

res.next()

yield表达式作为函数参数或者在表示式右边

//?1.?作为函数参数,yield部分并没有传递过去

function?a(){

console.log(...arguments)

}

function*?b(){

//?1.?函数参数使用yield语句,使用第一个next只会执行yield?部分

//?执行函数的部分需要调用骗下一个next才会有!

a(yield?2)

}

var?res=b()

//?console.log(res.next())

//?console.log(res.next())

/*

第一个打印:?{value:?2,?done:?false}

第二个打印:

undefined ?(属于函数a打印的参数,并没有接收到!)

{value:?undefined,?done:?true}

*/

//?2.?参数为多个yield语句,那么就一个个执行yield语句再执行函数

function?*?c(){

a(yield?3,yield?4)

}

var?res2=c()

console.log(res2.next());//{value:?3,?done:?false}

console.log(res2.next());//{value:?4,?done:?false}

console.log(res2.next())

/*

undefined?undefined

{value:?undefined,?done:?true}

*/

//?3.?yield语句作为表达式

function?*?e(){

var?c=yield?4;

console.log(c)

}

var?res3=e()

console.log(res3.next());//{value:?4,?done:?false}

console.log(res3.next())

/*

undefined

{value:?undefined,?done:?true}

*/

3. yield表达式与Iterator接口的关系

在Iterator中提过,对象的Symbol.iterator方法会在...运算符,for循环,forEach等中默认调用

调用生成器函数返回遍历器对象,调用遍历器对象的Symbol.iterator方法也是返回遍历器对象本身!

//?1.?Symbol.iterator方法

var?obj={}

obj[Symbol.iterator]=function*?(){

yield?11;

yield?2;

yield?3;

}

//?1.1?...运算符

console.log([...obj]);//[11,?2,?3]

//?1.2?for循环(即使有Iterator接口)

console.log(obj);// length为0,所以for循环没结果。

for(var?i=0;i

console.log(obj[i])

}

//?1.3?for...of循环,可以调用iterator接口

for(var?j?of?obj){

console.log(j);//11,2,3

}

//?1.4?forEach,报错(obj.forEach?is?not?a?function)

//?obj.forEach((item)=>{

//??console.log(item)

//?})

调用Symbol.iterator属性方法

function?*?a(){

yield?'w'

}

var?res=a();//?得到遍历器对象res

//?true

//?调用遍历器对象的Symbol.iterator方法,得到遍历器自身

console.log(res[Symbol.iterator]()===res);

二.next方法的参数

yield表达式本身没有返回值,但是可以使用一个变量保存该返回值

而next方法一般没有参数,但是可以设置一个参数,这个参数会被当做上一次的yield表达式的返回值

//?1.next参数会作为上一个yield表达式的值

function?*?g(){

for(var?i=0;true;i++){

var?res=yield?i;

if(res)?{

console.log(res)

i=-1;

}

}

}

var?res=g()

console.log(res.next());//{value:?0,?done:?false}

console.log(res.next());//{value:?1,?done:?false}

console.log(res.next());//{value:?2,?done:?false}

//?重点:上个yield表达式的返回值是变量res,所以此时变量res=-6

//?执行的代码为:

/*

if(res)?{

console.log(res)

i=-1;

}

//?然后还有下一轮的代码

for(...),

i++;所以i为0,然后执行到yield语句,返回0!

*/

console.log(res.next(-6))

console.log(res.next())

不使用变量保存yield xx

如果不使用一个变量保存yield xx,那么默认返回值就是undefined

//?1.?不使用变量保存yield表达式,返回值默认是undefined

function?*?foo(x){

var?y=10+(yield?x+3);

var?z=5+(yield?y-2)

return?x+y+z

}

//?1.1?此时传递参数x=3

var?a=foo(3);

//?1.2?此时执行yield?x+3;??结果为6

console.log(a.next());//{value:?6,?done:?false}

//?1.3?此时执行代码为:

/*

参数为10,10代替上次yield表达式返回值,所以y=10+10;

然后执行yield?y-2;??所以返回?20-2?=18

*/

console.log(a.next(10));//{value:?18,?done:?false}

/*

1.4?此时上次yield表达式yield?y-2的结果为100,所以z=5+100,

返回的是x+y+z,所以是3+20+105=128

20指的是y=10+10,?x依旧是参数不变

*/

console.log(a.next(100));//{value:?18,?done:?false}

//?2.?不使用变量保存,next不传递参数

function?*?d(){

return?10+(yield?9)

}

var?res2=d()

console.log(res2.next());//{value:?9,?done:?false}

//?此时执行的是?10+undefind,所以结果为NaN

console.log(res2.next());//{value:?NaN,?done:?true}

第一个next具有参数

由于next的参数表示的是上一个yield表达式的返回值,而第一个next方法前面没有返回值

所以js引擎会直接忽略第一次使用next方法的参数

function*?dataConsumer()?{

console.log(`Started?${yield?9}`);

console.log(`1.?${yield}`);

console.log(`2.?${yield}`);

}

let?genObj?=?dataConsumer();

//?第一个next方法传递参数,但是依旧没用

console.log(genObj.next('开始'));//{value:?9,?done:?false}

//?设置上一个yield表达式返回值为a

genObj.next('a')//?Started?a

genObj.next('b')//?1.b

如果希望第一个next方法就可以接受参数,设置第一个yield表达式的值

解决方法:

//?思路:执行掉第一个next()

function?wrapper(generatorFunction)?{

return?function?()?{

let?generatorObject?=?generatorFunction();

generatorObject.next()

return?generatorObject;

};

}

const?wrapped?=?wrapper(function*?()?{

var?a=yield?1;

var?b=yield?11;

var?c=yield?111;

console.log(a,b,c);//hello!?ddd!?rr!

});

var?res=wrapped();

console.log(res.next('hello!'));//{value:?11,?done:?false}

console.log(res.next('ddd!'));//{value:?111,?done:?false}

console.log(res.next('rr!'));//{value:?undefined,?done:?true}

三. for...of循环

for...of循环可以自动遍历generator函数运行时生成的Iterator对象,且此时不需要再次调用next方法

function?*?foo(){

yield?5;

yield?15;

yield?25;

yield?55;

}

for(var?item?of?foo()){

console.log(item);//5,15,25,55

}

for...of循环不会遍历done为true的部分

//?1.?return返回的遍历器对象的done为true

function?*a?(){

yield?5;

return?99;

yield?8?//?在return?语句之后的yield语句不会被for...of循环遍历到

}

for(var?item?of?a()){

console.log(item);//5

}

//?...运算符得到的也只有return之前的值

console.log([...a()]);//[5]

//?2.?遍历结束

function?*?b(){

yield?6;

yield?16;

}

for(var?item?of?b()){

console.log(item);//6,16

}

让对象遍历的方法:1. 通过调用generator函数增加接口 2. 设置对象的Symbol.iterator属性

//?1.?增加generator函数作为接口

function?*?gene(obj){

var?keys=Reflect.ownKeys(obj);

for(var?item?of?keys){

yield?[item,obj[item]]

}

}

var?obj={a:'w',g:'rrr'}

for(var?[key,val]?of?gene(obj)){

console.log(key,val)

/*?a?w

g?rrr?*/

}

//?2.?增加到对象的Symbol.iterator属性上面

var?one={'b':5555,c:1001}

function?*?f(){

var?keys=Object.keys(this);

for(var?item?of?keys){

yield?this[item]

}

}

one[Symbol.iterator]=f;

//?...运算符和Arry.from调用的都是iterator接口

console.log([...one]);//[5555,?1001]

console.log(Array.from(one));//[5555,?1001]

//?解构赋值

var?[x,y]=one

console.log(x,y);//5555?1001

四.throw()

generator生成器函数会返回一个遍历器对象,该对象具有一个throw方法

调用throw方法可以在函数体外抛出错误,然后在generaor函数体内捕获

但是如果抛出错误过多,那么无法全部捕获,就可能被外部的try-catch捕获掉

//?1.?多个语句

function?*g(){

try{

yield;

}catch(e){

console.log("err:",e);//err:?a

}

}

var?res=g()

console.log(res.next());//{value:?undefined,?done:?false}

try{

res.throw("a")

res.throw("b")

//?执行到此处不再执行(已经触发了catch),不会再打印了~

//?所以本语句不会捕获到错误c

console.log("执行到此处不再执行")

res.throw("c");

}catch(e){

console.log("外部捕获",e);//外部捕获?b

}

console.log("ddd");//ddd

//?2.?遍历器对象内部catch所在部分throw错误被外部接收

function?*?a(){

while(true){

try{

yield;

}catch(e){

throw?e;

}

}

}

var?res2=a()

res2.next()

try{

res2.throw("a");//?该错误被外部捕获。因为遍历器内部重新抛出了该错误

res2.throw("b")

}catch(e){

console.log("2外部捕获",e);//2外部捕获?a

}

//?3.?生成器函数没有在内部捕获错误,那么直接被外部捕获

function?*?b(){

yield?;

}

var?res3=b()

res3.next()

try{

throw?4??//?该错误直接被外部捕获

}catch(e){

console.log("err3:"+e);//err3:4

}

//?4.?没有部署catch捕获错误

function?*?c(){

yield?44;

}

var?res4=c()

res4.next()

//?此时抛出错误,没有被捕获,导致程序报错,中断执行

//?res4.throw("err");//平时测试.html:158?Uncaught?err

//?console.log("不再继续执行剩余代码")

//?5.?没有执行过next直接抛出错误

function?*?d(){

try{

yield?5;

}catch(e){

console.log(e)

}

}

var?res5=d()

//?因为还没有执行过next方法,所以没法绕过yield捕获错误!

res5.throw("eeee");//报错,但没捕获到Uncaught?eeee

throw()不会影响到下次遍历

throw方法被捕获以后,会附带执行下一条语句(相当于执行一次next方法)

function?*?a(){

try{

yield?;

}catch(e){

console.log('err',e)

}

yield?'b'

yield?3

}

var?res=a()

console.log(res.next());//{value:?undefined,?done:?false}

//?此时抛出错误,被捕获,最后附带执行一次next方法

console.log(res.throw())

/*

err?undefined

{value:?"b",?done:?false}

*/

console.log(res.next());//{value:?3,?done:?false}

console.log(res.next());//{value:?undefined,?done:?true}

gnerator函数体内错误影响到函数体外

//?generator函数体内抛出错误,在函数体外可以被捕获到

function?*?g(){

var?a=yield?'a';?//?变量a表示yield?'a'表达式的结果

//?由于第二个next传递了参数33,类型是Number,没有toUpperCase方法

//?所以会报错

var?y=yield?a.toUpperCase()

}

var?res=g()

console.log(res.next());//{value:?"a",?done:?false}

try{

res.next(33)

}catch(e){

//?可以捕获到函数体外的错误

//?err?TypeError:?a.toUpperCase?is?not?a?function

console.log('err',e);

}

generator内部和外部捕获错误

//?1.外部捕获到错误

function?*?a(){

yield?3;

throw?new?Error("eee");//Uncaught?eee

yield?5;

yield?15;

}

var?res=a()

console.log(res.next());//{value:?3,?done:?false}

try{

console.log(res.next())

}catch(e){

console.log("捕获到外部错误",e)

}

//?由于错误被外部捕获了,相当于函数内部崩溃了。所以接下来的next方法都不能执行yield表达式

console.log(res.next());//{value:?undefined,?done:?true}

console.log(res.next())

//?2.?内部捕获到错误(不会崩溃)

function?*?b(){

yield?7;

try{

throw?new?Error("eee")

}catch(e){

console.log("err",e)

}

yield?8;

yield?18;

}

var?res2=b()

console.log(res2.next());//{value:?7,?done:?false}

console.log(res2.next());//此时不会崩溃

/*

err?Error:?eee

{value:?8,?done:?false}

*/

console.log(res2.next());//{value:?18,?done:?false}

console.log(res2.next())//{value:?undefined,?done:?true}

五.return()

return 方法可以返回给定的值,并且终结遍历generator函数

return方法可以有一个参数,作为返回的遍历器对象的value属性

并且即使return之后调用next方法,依旧有yield表达式,done属性总是返回true

因为到那个时候,generator函数的遍历就终结了

function?*?g(){

yield?1;

yield?2;

yield?3;

yield?4;

}

var?res=g()

console.log(res.next());//{value:?1,?done:?false}

//?传递的数据a作为属性value的值

console.log(res.return('a'));//{value:?"a",?done:?true}

//?return方法之后的next方法返回的done属性为true

console.log(res.next());//{value:?undefined,?done:?true}

//?return不传递数据,那么value属性为undefined

console.log(res.return());//{value:?undefined,?done:?true}

return对应的yield语句在finally语句中

如果return对应的语句在try-finally代码块中,并且还在try语句块中

那么return传递的数据无效,返回的遍历器对象是finally代码块的第一个yield语句执行之后的结果

finally语句块最后的yield表达式执行完毕,继续执行next(),得到的value属性为return传递的数据

function*?numbers?()?{

yield?1;

try?{

yield?2;

yield?3;

}?finally?{

yield?4;

yield?5;

}

yield?6;

}

var?g?=?numbers();

console.log(g.next())?//?{?value:?1,?done:?false?}

console.log(g.next())?//?{?value:?2,?done:?false?}

//?1.?return传递的数据?此时对应于try-finally代码块中的try部分

//?此时返回的是finally后第一个yield表达式的数据

console.log(g.return(7))?//?{?value:?4,?done:?false?}

console.log(g.next())?//?{?value:?5,?done:?false?}

//?2.?此时finally语句块最后的yield表达式执行完毕

//?所以继续执行next(),得到的value属性为return传递的数据

console.log(g.next())?//?{?value:?7,?done:?true?}

//?3.?finally语句执行完毕,此时generaor遍历结束

console.log(g.next())?//?{value:?undefined,?done:?true}

yield和return 的区别

yield表达式与return语句既有相似之处,也有区别。

相似之处在于,都能返回紧跟在语句后面的那个表达式的值。

区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式。正常函数只能返回一个值,因为只能执行一次return;Generator 函数可以返回一系列的值,因为可以有任意多个yield。从另一个角度看,也可以说 Generator 生成了一系列的值,这也就是它的名称的来历(英语中,generator 这个词是“生成器”的意思)。

function?*?func(){

yield?1+3;

return?{value:1,done:true}

yield?4;

}

var?res=func();

console.log(res.next());//{value:?4,?done:?false}

console.log(res.next());//{done:?true,value:?{value:?1,?done:?true}}

//?return执行完毕,并不会记录generator状态,相当于generator遍历结束

console.log(res.next());//{done:?true,value:?undefined}

六.next/return/throw区别

next(),return(),throw()这三个方法本质上都是让generator函数恢复执行,并且使用不同的语句替换yield表达式

区别:

next()是将yield表达式替换成一个值

throw()是将yield表达式替换成一个throw语句

return()是将yield表达式替换成return语句

function?*?g(){

yield?4;

try{

yield?14;

}catch(e){

console.log(e);//throw

}

yield?24;

yield?34;

}

var?res=g()

//?next

console.log(res.next());//{value:?4,?done:?false}

//?throw

res.next();//?目的是为了进入try-caych语句块中

console.log(res.throw("throw"));//{value:?24,?done:?false}

//?return

console.log(res.return("return"));//{value:?"return",?done:?true}

七.yield*表达式

如果在gnerator函数内部,调用另一个generator函数,需要在前者的函数体内部,手动完成遍历

而ES6中提供了yield*表达式,作为解决方法,可以在一个generator函数里面执行另一个generator函数

function?*?foo(){

yield?2;

yield?12;

yield?22;

}

//?1.?for...of循环

function?*?g(){

yield?1;

for(var?item?of?foo()){

console.log('内',item)

yield?item;

}

yield?7;

}

var?res1=g()

console.log([...res1]);//[1,?2,?12,?22,?7]

//?2.?yield*表达式

function*?b(){

yield?1;

yield*?foo();

yield?7;

}

console.log([...b()]);//[1,?2,?12,?22,?7]

yield*表达式可以遍历所有遍历器对象

只有有iterator接口,都可以被yield*表达式遍历

function?*?foo(){

yield?2;

yield?12;

yield?22;

}

//?1.yield*表达式后面只能接遍历器对象

function*?b(){

yield?1;

//?1.1?字符串

//?yield*?"hello";//[1,?"h",?"e",?"l",?"l",?"o",?7]

//?1.2?遍历器函数

//?yield*??foo();//?[1,?2,?12,?22,?7]

//?1.3?返回未执行的遍历器函数

//?yield*??foo;//?TypeError:?undefined?is?not?a?function

//?1.4?数组

//?yield*?[4,5,6];//[1,?4,?5,?6,?7]

//?1.5?Set

yield*?new?Set([5,4,5,2]);//[1,?5,?4,?2,?7]

yield?7;

}

console.log([...b()]);

yield*表达式的generator函数有return语句

function?*f(){

yield?4;

return?'f'

yield?8;?//?不会继续执行

}

function?*?bar(){

yield?9;

var?name=yield*?f()

console.log("name:",name)

yield?5;

}

var?res=bar()

console.log(res.next());//{value:?9,?done:?false}

console.log(res.next());//{value:?4,?done:?false}

console.log(res.next());

/*如果yield*表达式return返回了数据,那么该数据就是返回值!

name:?f

{value:?5,?done:?false}

*/

console.log(res.next());//{value:?undefined,?done:?true}

//?例子2

function??*?a(){

yield?'a';

yield?'b';

return?'res'

}

function?*?b(){

var?r=yield*?a()

console.log('结果:',r)

}

var?res2=b()

console.log(res2.next());//{value:?"a",?done:?false}

console.log(res2.next());//{value:?"b",?done:?false}

console.log(res2.next())

/*

结果:?res

{value:?undefined,?done:?true}

*/

八.generator函数作为对象属性

//?形式1

var?obj={

*?ge(){

yield?3;

}

}

var?res=obj.ge()

console.log(res.next())

//?形式2

var?o={

g:function*?(){

yield?6

}

}

var?res2=o.g()

console.log(res2.next());//{value:?6,?done:?false}

九.generator函数的this

generator函数总是返回一个遍历器,ES6规定这个遍历器是generator函数的实例

继承了generator函数的prototype对象上的方法。

generator函数在this对象上面可以添加属性,但是实例对象拿不到属性

generator函数不能作为构造函数,不能使用new创建实例对象

//?1.?generator函数返回的遍历器对象是函数的实例

function?*?g(){}

g.prototype.func=function(){

return?'实例'

}

var?res=g()

//?返回的遍历器对象属于生成器函数的实例

console.log(res?instanceof?g);//true

console.log(res.func());//实例

//?2.?注意返回的是遍历器对象,而不是this对象

function?*?a(){

this.f="fff"

}

var?res2=a()

console.log(res2.f);//undefined

//?3.?generator函数没有构造器,不能new对象

//?new?a();//?报错Uncaught?TypeError:?a?is?not?a?constructor

如果想要generator函数返回一个正常的对象实例,既可以使用next方法又可以使用this

那么可以使用call来绑定generator函数内部的this到一个空对象

//?1.?空对象

var?obj={}

function?*?f(){

this.a='a'

yield?this.b=4;

yield?this.c='s';

}

//?2.使用call把generator函数内部的this绑定到空对象

var?res=f.call(obj);

console.log(res.next());//{value:?4,?done:?false}

console.log(res.next());//{value:?"s",?done:?false}

console.log(obj);//{a:?"a",?b:?4,?c:?"s"}

如果不想创建一个空对象来承接this对象,那么就使用call来绑定gnerator函数的原型

function?*?f(){

this.a=1;

yield?this.b=6;

yield?this.c=65;

}

//?使用call来绑定generator函数的原型

var?res=f.call(f.prototype)

console.log(res.next());//{value:?6,?done:?false}

console.log(res.next());//{value:?65,?done:?false}

console.log(res.a,res.b,res.c);//1?6?65

//?2.?如果想要通过new实例化对象

//?那么加多一个函数来间接实现

function?*?a(){

this.a=1;

yield?this.b=6;

yield?this.c=65;

}

function?father(){

return?a.call(a.prototype)

}

var?res2=new?father()

console.log(res2.next())

console.log(res2.next())

console.log(res2.a,res2.b,res2.c);//1?6?65

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200707A0VGF600?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券
http://www.vxiaotou.com