generator函数generator(生成器)是ES6标准引入
的新的数据类型。一个generator看上去像一个函数,但可
以返回多次。
generator和函数不同的是,generator由function定义(注意多出的号),并且,除了return语句,还可以用yield返回多次。
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}
//第一种调用方法
var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}
//第二种调用方法
for (var x of fib(10)) {
console.log(x); // 依次输出0, 1, 1, 2, 3, ...
}
什么是Generator?
语法上,可以把理解成,Generator 函数是一个状态机,封装了多个内部状态。
形式上,Generator 函数是一个普通函数。
整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器,异步操作需要暂停的地方,都用yield语句。
Generator函数特征:
(1)function 关键字和函数之间有一个星号(*),且内部使用yield表达式,定义不同的内部状态。
(2)调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。
但是,调用Generator函数后,函数并不执行,返回的也不是函数执行后的结果,而是一个指向内部状态的指针对象。
下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。即:每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
yield表达式和next()方法
Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。
E = mc^2
yield表达式就是暂停标志。
yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行。
使用yield需注意:
(1)yield语句只能用于function* 的作用域,如果function* 的内部还定义了其他的普通函数,则函数内部不允许使用yield语句。
(2)yield语句如果参与运算,必须用括号括起来。
return 方法与next方法的区别
1)return终结遍历,之后的yield语句都失效;next返回本次yield语句的返回值。
2)return没有参数的时候,返回{ value: undefined, done: true };next没有参数的时候返回本次yield语句的返回值。
3)return有参数的时候,覆盖本次yield语句的返回值,也就是说,返回{ value: 参数, done: true };next有参数的时候,覆盖上次yield语句的返回值,返回值可能跟参数有关(参数参与计算的话),也可能跟参数无关(参数不参与计算)。
遍历器对象的next方法的运行逻辑:
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
next()方法参数
表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。
for…of循环
可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。一旦next方法的返回对象的done属性为true,for…of循环就会中止,且不包含该返回对象。
实例理解generator
// 定义一个Generator函数
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
console.log(a.next()); // Object{value:6, done:false} 第二次运行next方法的时候不带参数,导致y的值等于2 * undefined(即NaN),除以3以后还是NaN
console.log(a.next()); // Object{value:NaN, done:false} 第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN。
console.log(a.next()); // Object{value:NaN, done:true}
var b = foo(5);
console.log(b.next()); // {value:6, done:false } 第一次调用b的next方法时,返回x+1的值6
console.log(b.next(12)); // {value:8, done:false } 第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;
console.log(b.next(13)); // {value:42, done:true } 第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。