Generator 是 ECMAScript6 提供的新特性之一,最大的特点是可以在其内部暂停执行。它可以用来生成一个迭代器。通过迭代器器不断往下执行,我们可以让代码在 Generator 内部暂停或继续执行。
什么是 Generator
Generator 函数的形式类似于一个普通函数,但其语法看上去与普通函数有以下区别。
- function 关键字与函数名之间有一个星号;
- 函数体内部可以使用
yield
和yield*
关键字定义迭代器的每个成员。
Generator 的行为也与普通函数不一样:
- 执行 Generator 函数会返回一个迭代器对象,而不是执行它的函数体内容。
- 它所生成的迭代器对象可以分段地执行 Generator 的函数体。
看个例子:
- 这段代码定义了一个 Generator,然后像一个函数一样调用它。
- 注意调用它时不会立即执行其内部代码,只会得到一个迭代器(numbers)。
- 在此之后每次调用迭代器的
next()
方法,就从函数体的开头(第一次调用next()
方法时)或上一次停下来的地方(第二次或之后调用next()
方法时)继续执行,直到遇到下一个yield
语句停止。 - 每一次执行
next()
方法都返回一个对象,对象的 value 属性就是当前yield
语句后面的表达式的值,done 属性表示迭代器是否已经遍历结束。 - 不用
yield
语句的 Generator,就是一个普通的暂缓执行函数,因为调用一次next()
就相当于执行函数。
遍历迭代器
既然 Generator 函数调用时返回的结果是一个迭代器对象,那么除了可以调用连续它的 next()
方法来遍历它之外,我们也可以用 for of
来遍历这个迭代器对象。例如:
这段代码会依次输出 1,2,3。
yield 和 yield*
|
|
这段代码会依次输出 1,2,3,4,5。yield
后面可以跟任何表达式,表达式的值将作为迭代器调用 next()
时的返回对象中的 value 属性值。yield*
后面只能跟一个另一个迭代器,效果相当于遍历该迭代器。可以理解为,当执行 next()
函数遇到了 yield*
语句时,执行权限被交给了 yield*
后面那个迭代器,当这个迭代器执行完(即done==false)时,执行权限又被交回来。
由于数组本来就支持迭代器遍历,所以 yield*
后面可以跟一个数据。例如:
yield 语句的值和 next() 方法
前面提到,调用迭代器的 next()
方法可以从函数头部或者上一个 yield
语句开始执行,一直执行到下一个 yield
语句。如果在调用 next()
方法时传入一个参数,则该参数会作为上一个 yield
语句的返回值。
- 第一次,调用 next() 时,第一个
yield
语句后面的表达式值是1,所以输出 {value: 1, done: false}。 - 第二次,调用 next(200) 时,传入 200,所以上一个
yield
语句的返回值被设置为 200,赋值给了 a。next(200) 的返回值是第二个yield
语句后面的表达式的值,也就是2,所以输出{value: 2, done: false}。 - 第三次,调用 next(300) 时,传入 300,所以上一个
yield
语句的返回值被设置为 300,赋值给了 b。此时代码走到了 return 语句,返回了 a+b 的值为500,而且迭代器编创结束,所以输出{value: 500, done: true}。
对 Generator 的使用
- Generator 天生提供了一种保存当前代码执行状态的能力,例如:12345678var clock = function*() {while (true) {console.log('Tick');yield;console.log('Tock');yield;}};
这个 clock 函数有两种状态:Tick 和 Tock,每执行一次迭代器的 next()
方法,状态就改变一次。同时用一个死循环来保证迭代器的 done
属性值永远是 false
。如果安装传统 JS 的写法,实现这样一个方法就需要有一个变量来保存当前的状态。相比之下,使用 Generator 无非更简洁。
- 由于调用迭代器的
next()
方法时可以传参数改变函数内的值,所以为写出灵活的代码提供了更多的可能性。123456789101112131415161718function* selfIncreaseGenerator() {var value = 0;var reset = false;while (true) {if (reset) {value = 0;reset = false;}reset = yield value++;}}var selfIncrease = selfIncreaseGenerator();console.log(selfIncrease.next().value); // 输出:0console.log(selfIncrease.next().value); // 输出:1console.log(selfIncrease.next().value); // 输出:2console.log(selfIncrease.next(true).value); // 输出:0console.log(selfIncrease.next().value); // 输出:1console.log(selfIncrease.next().value); // 输出:2
这里实现了一个计数器,每一次调用 next()
返回的值都比上一次大1。通过 next(true)
可以把计数器重置回0。