一.简介
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
领取专属 10元无门槛券
私享最新 技术干货