Generator函数是ES6中提供的一种异步解决方案,该函数同时也可以用来部署遍历器。
Generator函数的使用
Generator函数和普通的函数不一样,其function和函数名之间有个*,函数体内部用yield表达式来定义不同的状态。
function* gen(){
yield 1;
yield 2;
yield 3;
}
上面代码即为一个Generator函数,与普通函数一样,Generator函数也是在函数名后面加括号传入参数来调用参数的,但函数不会立即执行,需要使用next才会执行,且是执行到yield表达式后的那一句停止,等待下一个next。
function* gen(){
yield 1;
yield 2;
yield 3;
}
var g=gen()
g.next() // {value: 1, done: false}
g.next() // {value: 2, done: false}
g.next() // {value: 3, done: false}
g.next() // {value: undefined, done: true}
如上面代码所示,每次调用next都会让函数往后执行,且返回一个对象,value为yield表达式后面的内容,done表示遍历是否结束,为true时就结束。
yield表达式
yield表达式是Generator函数中的暂停标志,若后面没有yield表达式,有return语句时,执行到return语句,返回value为return后面的内容的对象,没有return语句时,返回value为undefined的对象,done都为true。如果在两个yield表达式中间有return语句,则done会提前变为true。
function* gen(){
yield 1;
yield 2;
return 3;
yield 4;
}
var g=gen();
g.next(); // {value: 1, done: false}
g.next(); // {value: 2, done: false}
g.next(); // {value: 3, done: true}
g.next(); // {value: undefined, done: true}
上面代码可以看到,因为提前return了,所以在value为3时done就为true了,不会执行到下面的yield 4。
若yield表达式后面是一个表达式,那么会在next语句执行到这一句时才进行求值,即惰性求值。
function* gen() {
yield 123 + 456;
}
像上面代码中,123+456就是在执行到这一句时才求值的。
当然,Generator函数里面可以没有yield语句,此时的Generator函数就变成一个暂缓执行的函数,当调用next时就会执行整个函数,但没调用和next之前,函数是不会执行的。
function* gen(){
console.log('no yield');
}
var g=gen()
g.next();
// {value: undefined, done: true}
// no yield
要注意的是,yield表达式只能在Generator函数中使用,在其他地方使用会报错。
(function (){
yield 1;
})()
// Uncaught SyntaxError: Unexpected number
即使在Generator函数中,若在该函数中再嵌套了一个非Generator函数,而yield语句又在这个函数里面,则也会报错。
function* gen() {
let arr = [1, 2, 3];
arr.map(function(val) {
yield val;
})
}
// Uncaught SyntaxError: Unexpected identifier
上面代码中,yield语句实际上是在map参数中的函数的,而该函数不是一个Generator函数,所以在声明函数的时候就报错了。
另外,yield若要用在另一个表达式中,必须放在圆括号内,否则会报错。
function* gen(){
console.log(2+yield 3 );
}
// Uncaught SyntaxError: Unexpected identifier
function* gen(){
console.log(2+(yield 3));
}
// 不报错
Generator的方法
Generator函数返回一个遍历器,该遍历器有三个方法使函数走出“冻结”的状态。
next方法
该方法使Generator函数执行到下一条yield语句,若之后没有yield语句,则执行到函数尾部,第一个次调用next时算启用Genertor函数。
该方法返回一个有value属性和done属性的对象,value属性的值为yield表达式后的内容,done表示遍历是否已经结束。
function* gen(){
yield 1;
yield 2;
return 3;
}
var g=gen();
g.next()
// {value: 1, done: false}
g.next()
// {value: 2, done: false}
g.next()
// {value: 3, done: true}
g.next()
// {value: undefined, done: true}
throw方法
该方法会在Generator函数体外抛出一个错误,在函数体内处理。
function* gen(){
try{
yield;
}catch(e){
console.log(`Generator内部 ${e}`);
}
}
var g=gen();
g.next();
try{
g.throw('error');
}catch(e){
console.log(`Generator 外部 ${e}`)
}
// Generator内部 error
上面代码可以看到,最后throw抛出的错误被Generator函数内部的catch捕获了。同时也可以看到,throw方法的参数被catch获取到了,若没有传入参数,catch获取的是undefined。
若Generator方法内部没有try catch语句,那么错误要在外部捕获
function* gen(){
yield;
}
var g=gen();
g.next();
try{
g.throw('error');
}catch(e){
console.log(`Generator 外部 ${e}`)
}
// Generator 外部 error
若内部和外部都没有部署try...catch语句,那么将会报错。
function* gen(){
yield;
}
var g=gen();
g.next();
g.throw('error');
// Uncaught error
要注意的是,throw方法使用时会执行下一条yield语句,即顺带执行一条next方法
var gen = function* gen(){
try {
yield console.log('a');
} catch (e) {
// ...
}
yield console.log('b');
yield console.log('c');}
var g = gen();
g.next() // a
g.throw() // b
g.next() // c
上面代码中,throw方法抛出错误的同时执行了下一条yield语句console.log(‘b’),因为是在try...catch语句中抛出错误的,所以不影响下面语句的执行。
return方法
该方法返回给定的值并终结函数的遍历。即返回一个value为传入的参数,done为true的对象。
function* gen(){
yield 1;
yield 2;
return 3;
}
var g=gen()
g.next()
// {value: 1, done: false}
g.return(4)
// {value: 4, done: true}
g.next()
// {value: undefined, done: true}
若Generator函数中有try...finally语句,则return方法使用后会等到finally块内的语句执完后才停止遍历。return方法的参数作为最后一次遍历返回的对象的value值。
function* gen(){
yield 1;
try{
yield 2;
yield 3;
}finally{
yield 4;
yield 5;
}
yield 6;
}
var g=gen()
g.next()
// {value: 1, done: false}
g.next()
// {value: 2, done: false}
g.return(10)
// {value: 4, done: false}
g.next()
// {value: 5, done: false}
g.next()
// {value: 10, done: true}
g.next()
// {value: undefined, done: true}
上面代码中,在使用return方法后返回了value为finally语句中第一个yield表达式后的值的对象,且done为false,说明遍历还未结束。
yield*表达式
要在一个Generator函数里面调用另一个Generator函数,像普通函数一样调用是无效的。因为这样的调用并没有让Generator函数开始执行。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "y"
通过使用yield*表达式,就能在一个Generator函数里面调用另一个Generator函数。
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
for (let v of bar()){
console.log(v);}
// "x"
// "a"
// "b"
// "y"
实际上,yield*表达式后面不只是可以放另一个Generator函数,只要是有Iterator接口的结构,都可以放在其后。yield*其实是表示遍历后面的带Iterator的结构。
function* gen(){
yield* [1,2,3];
}
//等同于
function* gen(){
for (let i of [1,2,3])
yield i;
}
var g=gen();
g.next()
//{value: 1, done: false}
g.next()
//{value: 2, done: false}
g.next()
//{value: 3, done: false}
g.next()
//{value: undefined, done: true}
由此可以看出,yield*表达式其实就是用一个for...of来遍历后面的结构,同上,字符串也同样可以。
function* gen(){
yield* 'yield';
}
var g=gen()
g.next()
// {value: "y", done: false}
g.next()
// {value: "i", done: false}
g.next()
// {value: "e", done: false}
g.next()
// {value: "l", done: false}
g.next()
// {value: "d", done: false}
g.next()
// {value: undefined, done: true}
作为对象的Generator函数写法
let obj = {
myGeneratorMethod: function* () {
// ···
}};
Generator函数的一般写法如上代码,但也可以简写为下面的代码
let obj = {
* myGeneratorMethod() {
//···
}};
Generator函数的this
Generator函数返回的遍历器是Generator函数的实例,会继承Generator函数的原型,但要注意的是,函数返回的是遍历器对象,不是this对象。
function* g() {
this.a = 11;
}
let obj = g();
obj.next();
obj.a // undefined
上面代码中,函数g中在this对象上声明赋值了一个属性a,但使用obj那不到这个a。
同时,Generator函数不能使用new运算符一起使用,否则会报错。
function* gen(){
yield 1;
yield 2;
}
new gen()
// Uncaught TypeError: gen is not a constructor
上面代码可以看到,报错提示gen不是一个构造函数。
参考自阮一峰的《ECMAScript6入门》