generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。
我们先来看一下函数的写法,一个函数是一段完整的代码,调用一个函数就是传入参数,然后返回结果
function foo(x) {
return x + x;
}
var r = foo(1); // 调用foo函数
函数在执行过程中,如果没有遇到return语句(函数末尾如果没有return,就是隐含的return undefined;),控制权无法交回被调用的代码。
generator和函数类似,声明的时候使用function* 来声明,内部通过yeild来阻断代码执行,并返回相应的值,在次执行next时继续后面代码执行。
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
调用的时候示例
var test = foo(1);
test.next();//{value: 2, done: false}
test.next();//{value: 3, done: false}
test.next();//{value: 4, done: true}
test.next();//{value: undefined, done: true}
yield关键字使生成器函数执行暂停,yield关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本的return关键字。
yield关键字实际返回一个IteratorResult对象,它有两个属性,value和done。value属性是对yield表达式求值的结果,而done是false,表示生成器函数尚未完全完成。
一旦遇到 yield 表达式,生成器的代码将被暂停运行,直到生成器的 next() 方法被调用。每次调用生成器的next()方法时,生成器都会恢复执行,直到达到以下某个值:
yield,导致生成器再次暂停并返回生成器的新值。 下一次调用next()时,在yield之后紧接着的语句继续执行。 throw用于从生成器中抛出异常。这让生成器完全停止执行,并在调用者中继续执行,正如通常情况下抛出异常一样。 到达生成器函数的结尾;在这种情况下,生成器的执行结束,并且IteratorResult给调用者返回undefined并且done为true。 到达return 语句。在这种情况下,生成器的执行结束,并将IteratorResult返回给调用者,其值是由return语句指定的,并且done 为true。 如果将可选值传递给生成器的next()方法,则该值将成为生成器当前yield操作返回的值。
在生成器的代码路径中的yield运算符,以及通过将其传递给Generator.prototype.next()指定新的起始值的能力之间,生成器提供了强大的控制力。
在使用yield阻断程序运行时需要注意一点yield 使用的地方必须是能够返回函数调用的位置,比如说
function* foo(x) {
setTimeout(()=>{
yield x;
},1000)
}
如此写必然会产生报错,因为在异步函数内返回是无法返回到函数调用者身上。
另外在不能结束循环的位置使用也是无法实现效果,例如map
function* foo(x) {
var x = [1,2,3,4,5,6];
x.map(item=>{
yield item
})
}
这种方式和在异步函数中写效果是一样的,都会报错,可以将map改为for循环或者while循环即可。 例如要编写一个产生斐波那契数列的函数,可以这么写:
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}
调用的时候可以使用for of来调用
for (var x of fib(10)) {
console.log(x); // 依次输出0, 1, 1, 2, 3, ...
}
generator和普通函数相比,有什么用?
因为generator可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数,利用这一点,写一个generator就可以实现需要用面向对象才能实现的功能。
比如一个自增函数,如果用普通的函数来写的话
var current_id = 0;
function next_id() {
current_id ++;
return current_id;
}
由于函数无法保存状态,故需要一个全局变量current_id来保存数字。
然而用generator来写就比较简单了,不需要声明全局变量
function next_id() {
var current_id = 0;
do{
yield current_id;
current_id++
}while(true)
}
这样每次调用next都会生成一个id,同时不用声明全局变量,减少全局变量污染的情况。