ES6学习笔记9:生成器函数

生成器函数

生成器对象(Generator Object)是一种『运行,暂停,运行,暂停,…,运行,结束』的模式,其中每次暂停都会生成一个值。生成器对象由生成器函数(Generator Function)返回创建。

先来一个简单的例子。

1
2
3
4
5
6
7
8
9
10
11
12
// 生成器函数
function *gen() {
yield 1;
yield 2;
yield 3;
}
// 生成器对象
let it = gen();
console.log(it.next()); // {value: 1, done: false}
console.log(it.next()); // {value: 2, done: false}
console.log(it.next()); // {value: 3, done: false}
console.log(it.next()); // {value: undefined, done: true}

注意上面生成器函数的定义方式,不同于上一篇总结中的定义方式,这里我们把号放在了函数名前,而上一篇总结中放到了function关键字后,实际上这两种方式都是合法的,在这里我们推荐使用函数名前加号,这样表示生成器函数实际上还是一种函数,只不过是一种特殊的函数。

return关键字

在生成器函数中,同样也是可以使用return关键字的。

1
2
3
4
5
6
7
8
9
function *gen() {
yield 1;
yield 2;
return 3;
}
let it = gen();
console.log(it.next()); // {value: 1, done: false}
console.log(it.next()); // {value: 2, done: false}
console.log(it.next()); // {value: 3, done: true}

注意这里最后一行在返回数字3之后,done的值直接为done,而不是之前的必须等到生成一个undefined才得到done。

此外在实际使用过程中并不推荐在生成器函数中使用return关键字,因为在大多数情形中生成器函数都是在一个循环中生成值,因此return返回的值可能永远不能获取。

yield关键字

yield关键字会生成一个值,同时下一次调用next()传入的值将替代上一次的yield表达式,如果yield不显式生成值,则会生成undefined,如果next()不传入任何值,则该yield表达式将被undefined替代。

1
2
3
4
5
6
7
8
9
10
11
12
// 注意这里foo不是一个生成器函数
function foo(x) {
console.log("x: " + x);
}
function *bar() {
yield; // 这里暂停
foo( yield ); // 第二次暂停等待传入值
}
let it = bar();
it.next(); // {value: undefined, done: false}
it.next(); // {value: undefined, done: false}
it.next('test'); // {value: undefined, done: true},同时控制台输出"x: test"

请读者再把上面解释yield特性的加粗段落再读一遍,结合上述代码充分理解一下:

首先我们定义了一个普通函数foo(),该函数做最简单的传值输出;

然后我们定义了生成器函数bar(),还生成器一共依次生成两个值,由于未显示声明任何值,因此都是返回undefined;

后续代码中我们生成了一个生成器对象,并依次调用了三次next(),关于三次调用next()的结果,很好理解,所有返回值都是undefined,第三次done表示已经生成结束。但是我们注意到第三次调用next()时,调用了foo()函数,yield关键字会生成一个值,同时下一次调用next()传入的值将替代上一次的yield表达式,第一次调用时不发生任何事,第二次调用时我们没有传入任何值,因此一个yield表达式的值就是undefined,但是第三次调用时我们传入了'test',所以第二个yield表达式的值就是'test',生成器继续执行,即foo('test')

上面的文字有些绕口,为了更好地理解yield,我们再举一个例子来理解yield关键字。

生成器对象和生成器函数

1
2
3
4
5
6
7
8
9
10
function *foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var it = foo( 5 );
// 注意第一次调用即使传了值也会被丢弃
console.log( it.next() ); // { value:6, done:false }
console.log( it.next( 12 ) ); // { value:8, done:false }
console.log( it.next( 13 ) ); // { value:42, done:true }

注意,生成器函数的也是可以传值的,这里传值的用法并不与普通函数有任何区别。

我们对最后三行逐行进行分析:

第一次it.next()时,一开始传入的初始值为5,生成器函数中的第一次yield(x + 1)生成的是6,这个毫无疑问;

第二次it.next(12),传入了1212替换了的第一个yield表达式,因此y的值变成了2 * 12也就是24,因此第二次生成的值是yield(y / 3)也就是8

第三次it.next(13),传入1313替换第二个yield表达式,因此z等于13,因此第三次生成的值是yield(x + y + z)5 + 24 + 1342

结合for of使用

需要注意的是,在绝大多数情况下,生成器函数都是循环生成值,并结合for of使用,具体可以查阅[参考3]中生成器对象和生成器函数小节中的斐波那契数列例子。

知识点总结

  1. 理解生成器的概念;
  2. 充分理解yield关键字,知道在生成器中yield和return的区别;
  3. 熟练结合for of使用生成器。

参考

  1. BabelJS - Learn ES2015
  2. The Basics Of ES6 Generators - About Kyle Simpson
  3. ES6学习笔记8:迭代器和for of - 传不习乎