文/anMoo韩魔
Generator函数
语法上面看,Generator函数可以理解成一个状态机,封装了多个内部状态,执行一个Generator函数就会返回一个遍历器对象,可以依次遍历Generator函数内部的每一个状态。
特征:
- function命令与函数名之间有一个星号
- 函数体内使用yield语句定义不同的内部状态
- 可以交出函数的执行权,即暂停执行
function* statusgenerator(){
yield:'A';
yield:'B';
return 'ending';
};
var sg = statusGenerator(); //undefined
上面的sg是调用statusgenerator函数后返回的遍历器对象,所以相对具有next()方法。
var sg = statusGenerator(); //undefined
sg
//statusGenerator{[[GeneratorStatus]]:"suspended",[[GeneratorReceiver]]:"Window,[[GeneratorLocation]]:Object"}
sg.next();
//Object{value:"A",done:false}
sg.next();
//Object{value:"B",done:false}
sg.next();
//Object{value:"ending",done:true}
当使用next方法的时候,函数内部的指针就会从那个上次停止的地方开始指向下一条yield就停止。这样可以使Generator函数分段执行,调用next()方法就可以恢复执行。
因为Generator函数可以用来返回每个状态的结果,所以可以达到状态机的效果。
yield
Generator函数返回的遍历器对象只有调用next方法才会遍历下一个内部状态,所以其实是提供了一种可以暂停执行的函数,yield语句就是暂停的标志,也就是异步两个阶段的分界线。
这样做的最大优点:代码的写法非常像同步操作,如果去除yield命令,简直一摸一样。
遍历器对象的next方法的运行逻辑:
- 遇到yield就暂停执行后面的操作,并将紧跟yield后的表达式的值作为返回的对象的value属性值。
- 下一次调用next方法时在继续往下执行,直到遇到下一个yield。
- 如果没有遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值作为返回对象的value属性值。
- 如果函数没有return语句,则返回的对象value属性值为undefined。
需要注意的是:
- Generator函数可以不用yield语句,yield语句不能用在普通函数中,会报错。
- yield语句如果用在一个表达式中,必须放在圆括号里面。如果用作函数参数或者用于赋值表达式的右边,可以不加括号。所以容易引起误解的地方应该把yield用圆括号括起来。
function* g(){
console.log('hello' + (yield 'world'));
}
Generator函数和Symbol.iterator方法
任何一个对象的Symbol.iterator方法等于该对象的遍历器对象生成函数,也就是说在调用这个函数的时候会返回该对象的一个遍历器对象。
因为Generator函数调用的时候就是返回一个遍历器函数。当你需要对一个对象部署Symbol.iterator方法时,可以使用Generator函数。
function* g(){};
var gen = g();
gen[Symbol.iterator]() === gen // true
Next()方法传参
Yield语句本身没有返回值,或者说是返回值是undefined,next方法可以带一个参数,该参数会当做上一条yield语句的返回值。
function* f(){
for(var i = 0;true; i++){
var reset = yield i;
if(reset){
i = -1
}
}
}
var g = f();
g.next(); // Object{value:0,done:false}
g.next(); // Object{value:1,done:false}
g.next(true); // Object{value:0,done:false}
最后next的参数true作为reset=yield i
的返回值,所以i
就等于-1。
从这个例子可以看出,Generator函数从暂停到恢复运行,其上下文状态是不变的,通过next方法的参数可以在Generator函数开始运行后继续向函数体内部注入值,也就是说,在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数的行为。
Generator运用For...of循环
Generator返回的是遍历器,所以可以用For... of循环Generator函数,可以不用再调用next方法。
除了for...of循环,扩展运算符,解构赋值,Array.from方法内部都是遍历器接口,意味着可以将Generator函数返回的Iterator对象作为参数。
function* f(){
yield 'a';
yield 'b';
yield 'c';
return 'd'
}
for(v of f()){
console.log(v)
}
//返回的依次是:a,b,c,undefined
Generator.prototype.throw()
可以在函数体外抛出错误,然后在函数体内捕获。
var g = function(){
while(true){
try{
yield;
}catch(e){
if(e!='a') throw e;
console.log('内部捕获',e)
}
}
}
i=g();
i.next();// Object{value: undefined, done: false}
i.throw('a') // 内部捕获 a
// Object{value: undefined, done: false}
注意:
- 不要混淆遍历器对象的throw方法和全局的throw方法
- 如果Generator内部没有部署try...catch代码块,那么throw方法抛出的错误将被外部try...catch代码捕获
- 如果Generator内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误不影响下一次遍历。
Generator.prototype.return()
Generator函数返回的遍历器对象,有一个return方法,可以返回给定的值,并且终结Generator函数的遍历。如果return方法调用时不提供参数,则返回值的value属性为undefined。
var gen = function(){
yield 1;
yield 2;
yield 3;
};
var g = gen();
g.next(); // Object{value:1,done:false}
g.return('hello') // Object{value:"hello",done:true}
g.next(); // Object{value:undefined,done:true}
Yield*语句
如果在Generator函数内部调用另外一个Generator函数,默认情况下是没有效果的。需要使用yield*语句,在一个Generator函数里执行另外一个Generator函数。
这样相当于在外面的Generator函数中遍历另外一个Generator函数,然后yield
var inner = function* (){
yield 'a';
yield 'b';
}
var gen = function* (){
yield 1;
yield 2;
yield* inner();
yield 3;
}
var g = gen();
g.next() // 1 false
g.next() // 2 false
g.next() // "a" false
g.next() // '"b' false
g.next() // 3 false
g.next() // undefined true
上下两种方法得到的结果是一致的。
var gen = function* (){
yield 1;
yield 2;
for(v of inner()){
yield v;
};
yield 3;
}
var g = gen();
g.next() // 1 false
g.next() // 2 false
g.next() // "a" false
g.next() // '"b' false
g.next() // 3 false
g.next() // undefined true
如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此会遍历数组成员。
实际上,任何数据结构只要有Iterator接口,就可以用yield*遍历。
如果yield命令后面跟的是一个遍历器对象,那么需要在yield命令后面加上星号,表明返回的是一个遍历器对象,这个被称为yield*语句。相当于for...of的一种简写形式。
Generator函数可以作为对象的属性,简写成如下形式:
let obj = {
*myGeneratorMethod(){
yield;
}
}
Generator函数总是返回一个遍历器,这个遍历器是Generator函数的实例,继承了Generator函数prototype对象上的方法。
Generator函数的用途
- 异步操作的同步化表达
- 控制流管理
- 部署Iterator接口
- 作为数据结构
注:以上所有内容都是本人的学习笔记和总结,仅供学习和参考,如果有遗漏或者不当的地方请谅解,请勿转载。