生成器 generator
语法
1 | function *foo() { |
*
的位置可以改变,写成下面的几种形式都可以:
1 | function *foo() { .. } |
在对象字面量中,函数成员有简化形式,生成器同样也有:
1 | var a = { |
调用生成器
调用生成器和调用普通的函数一样,函数名加上 ()
:
1 | foo(); |
和普通函数的主要差别是调用生成器时,并不是直接运行生成器中的代码。而是会生成一个迭代器来控制生成器代码的运行。
yield
生成器使用了一个新的关键字 yield
:
1 | function *foo() { |
普通的函数正常情况下从调用开始运行,一直到 return ..
语句结束运行。 而生成器则不是这样,它可以在代码运行的过程中停止, yield
关键字出现的位置就是停止点。
在上面的例子中,前两行代码会先运行,然后遇到 yield
,后续的代码会暂停运行。使用迭代器 next()
恢复运行之后,最后一行代码才会执行。 注意,yield
能够在一个生成器中出现多次,也可以出现在循环当中:
1 | function *foo() { |
yield
不仅仅可以将值从函数中输出来,也可以通过迭代器的 next(..)
输入进函数:
1 | function *foo() { |
生成器首先在第一次停止的时候输出一个10,然后使用 it.next(..)
恢复生成器的时候,从 next(..)
传进来的参数会取代 yield 10
赋值给 x
。
yield ..
类似于一个赋值表达式 a=..
, 赋值表达式可以出现的地方, yield
都可以出现:
1 | var a, b; |
yield *
yield *
叫做yield 委托,在语法上 yield * ..
和 yield
表现的一样:
1 | function *foo() { |
yield * ..
后面需要跟一个可迭代的值,然后调用这个迭代器,然后让迭代器接管这个生成器,直到迭代完毕:
1 | function *foo() { |
数组 [1,2,3]
会生成一个迭代器遍历这些值,而 *foo()
生成器会一步一步输出这些值。
另外 yield *..
后面还可以跟一个生成器:
1 | function *foo() { |
*foo()
输出的值,在 *bar()
中也会输出。看一个例子:
1 | function *foo() { |
1,2,3
首先从 *foo
中输出,然后再从 *bar()
中输出,4
是 *foo()
的返回值,会用来给 x
赋值。
生成器的控制与迭代器一样,通过 next(), return(), throw()
控制他继续运行,或者提前结束又或是抛出异常。
1 | function *foo() { |
上面这个例子说明了,可以通过多个迭代器同时控制一个生成器,并且互相之间相互不干扰。
1 | function *foo() { |
上面这个例子说明了foo..of..
可以进行迭代器迭代工作,利用 return
可以提前结束生成器的执行,并且完成 finally
语句块中的清理工作。
1 | function *foo() { |
上面是一个错误处理的例子,首先调用生成器,返回一个迭代器赋值给 it
, 调用 it.next()
,输出 *foo
中的 try
块中的 yield 1
的结果。
然后主流程中的 try
语句中调用 it.throw()
抛出一个异常,生成器捕捉到这个基础进行输出。然后生成器继续执行到 yield 2
语句中止,输出 { value: 2, done: false }
。然后主流程中的 try
块继续运行 it.next()
, 生成器抛出一个异常, it.next()
没有得到{done:true}
的结果,这个异常被主流程捕捉到进行输出。
剖析生成器
为了更好的理解生成器的原理,我们进行人工转换,将生成器转换成ES5的代码。
首先,我们的生成器非常简单:
1 | function *foo() { |
这是一个函数,调用这个函数返回一个迭代器:
1 | function foo(){ |
接下来分析这个 next()
, next()
返回值是 {value: 42, done: false}
和 {value: undifined, done: true}
。所以有:
1 | function foo(){ |
生成器到完成状态,要调用两次next,那么我们设置两个状态:
1 | function foo(){ |
利用一个闭包来控制调用 next
的状态。但是这与我们的生成器还是有点不同,在第二次调用 it.next()
时,可以传入一个参数给 x
赋值,并且打印出来,所以我们可以改写一下:
1 | function foo(){ |
那么一个生成器就这样该写出来了。
总结
ES6 引入了生成器,生成器提供了很强大的功能:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。