1. Generator函数是ES6提供的一种异步变成解决方案,语法行为与传统函数完全不同。

Generator函数有多种理解角度,语法上,可以将Generator函数理解为状态机,封装了多个内部状态。

执行Generator函数会返回一个遍历器对象,可以依次遍历Generator函数中的每一个状态。

Generator函数有两个特征:一是,function关键字紧跟一个*号;二是,函数体内部使用yield表达式,定义不同的内部状态。

yield能返回跟在语句后面的表达式的值。

  1. function* helloWorldGenerator() {
  2. yield 'hello'
  3. yield 'world'
  4. return 'ending'
  5. }
  6. var it = helloWorldGenerator()
  7. console.log(it.next())
  8. console.log(it.next())
  9. console.log(it.next())

如果helloWorldGenerator函数没有return ‘ending’,那么第三次调用next函数的返回值中,value = undefined.

  1. // 星号位置,都可以
  2. function* generator() { }
  3. function *generator() { }
  4. function * generator() { }
  5. function*generator() { }

yield 表达式

yield表达式就是Generator函数暂停执行的标志。

遍历器的next方法运行如下:

  • 第一次执行next(),遇到yield表达式,就暂停后面的操作,并将紧跟yield后的表达式的值,作为返回对象value的值。
  • 下一次调用next方法时,再继续执行,直到遇到下一个yield表达式。
  • 如果没有遇到yield表达式,就一直运行到函数结束,直到return语句为止,并将return后的表达式的值,作为返回对象的value属性的值。
  • 如果没有return语句,那么返回对象的value属性的值为undefined。

与Iterator接口的关系

对于任意一个对象的System.iterator方法,等于该对象的遍历器生成函数,调用该函数
会返回该对象的一个遍历器对象。

  1. var myIterator = {};
  2. myIterator[Symbol.iterator] = function*() {
  3. yield 1;
  4. yield 2;
  5. yield 3;
  6. yield 4;
  7. }
  8. console.log([...myIterator])

next方法的参数

yield表达式本身没有返回值,或者说总是undefined。next()方法可以带一个参数,该参数就会被当做上一个yield表达式的返回值。

该例子可以证明,yield返回值总是undefined,除非next方法传参。

  1. function* f() {
  2. for (var i = 0; true; i++){
  3. var reset = yield i;
  4. console.log(reset)
  5. if (reset) {
  6. i = -1;
  7. }
  8. }
  9. }
  10. var g = f();
  11. console.log(g.next());
  12. console.log(g.next());
  13. console.log(g.next());
  14. console.log(g.next());
  15. console.log(g.next());
  16. console.log(g.next());
  17. console.log(g.next());
  18. // console.log(g.next(true));

下面这个例子说明 yield表达式本身是返回undefined的,除非next方法传参

  1. function* foo(x) {
  2. var y = 2 * (yield x + 1)
  3. var z = yield y / 3
  4. return x + y + z
  5. }
  6. var a = foo(5)
  7. // 6
  8. console.log(a.next())
  9. NaN
  10. console.log(a.next())
  11. NaN
  12. console.log(a.next())
  13. var b = foo(5)
  14. // 6
  15. console.log(b.next())
  16. 8
  17. console.log(b.next(12))
  18. // 42 y=24 z=13 x=5
  19. console.log(b.next(13))

下面再来一个面试题,看看结果是什么

  1. function* dataConsumer() {
  2. console.log('started');
  3. console.log(`1. ${yield}`)
  4. console.log(`2. ${yield}`)
  5. return 'result'
  6. }
  7. var it = dataConsumer();
  8. // started
  9. console.log(it.next())
  10. // 1. a
  11. console.log(it.next('a'))
  12. // 1. b
  13. console.log(it.next('b'))

for…of 循环

for…of循环能够遍历Generator函数运行时生成的Iterator对象,并且不需要调用next方法。

  1. function* foo() {
  2. yield 1;
  3. yield 2;
  4. yield 3;
  5. yield 4;
  6. yield 5;
  7. return 6;
  8. }
  9. let it = foo();
  10. for (let key of it) {
  11. console.log(key)
  12. }

如果你运行这个代码就会发现,return的6没有输出,这是因为next方法返回对象的done属性为true时,for…of就会停止执行。

除了for…of外,扩展运算符(…),解构赋值和Array.form方法内部调用的,都是遍历器接口。这意味着他们都可以将Generator函数返回的遍历器对象作为参数。

  1. function* foo() {
  2. yield 1;
  3. yield 2;
  4. yield 3;
  5. return 4;
  6. yield 5;
  7. }
  8. // 扩展运算符
  9. console.log([...foo()])
  10. // Array.from
  11. console.log(Array.from(foo()))
  12. // 解构赋值
  13. var [a, ...b] = foo()
  14. console.log(a)
  15. console.log(b)

Generator.prototype.throw()

Generator函数返回的遍历器对象,都有一个throw方法,可以在函数体外跑出错误,然后在Generator函数体内部捕获。

  1. function* g() {
  2. try {
  3. yield;
  4. } catch (e) {
  5. console.log('函数内部捕获错误', e)
  6. }
  7. return 123123
  8. }
  9. var it = g();
  10. console.log(it.next())
  11. try {
  12. it.throw('a');
  13. it.throw('b');
  14. it.throw(new Error('记住,这么抛错误!'))
  15. } catch (e) {
  16. console.log('函数体外捕获错误', e)
  17. }

Generator.prototype.return()

Generator函数返回的遍历器对象,还有个return()方法,可以返回给定的值,并且终结遍历Generator函数。

  1. function* foo() {
  2. yield 1;
  3. yield 1;
  4. yield 1;
  5. return 4;
  6. }
  7. var it = foo();
  8. console.log(it.next())
  9. console.log(it.return(666))

如果Generator函数内部有try…finally代码块,且正在执行try块,那么return方法会推迟到finally块执行完后再执行

  1. // 即使return 也会先执行finally
  2. function* foo2() {
  3. yield 1
  4. try {
  5. yield 2
  6. yield 3
  7. } finally {
  8. yield 4;
  9. yield 5;
  10. }
  11. }
  12. var it2 = foo2();
  13. console.log(it2.next())
  14. // 此处注意,要是没有执行到try内部,则finally不会执行。
  15. // console.log(it2.next())
  16. console.log(it2.return(555))
  17. console.log(it2.next())

next()、throw()、return() 的共同点

他们的作用都是让Generator函数恢复执行,并且使用不同的语句替换yield表达式。

next()是将yield表达式替换为一个值。

  1. function* foo() {
  2. let res = yield 1;
  3. return res
  4. }
  5. var it = foo()
  6. console.log(it.next())
  7. console.log(it.next(123123))

第二个next(123123)相当于把yield表达式的值替换为123123,如果next参数为空,相当于替换为undefined;

throw()是将yield表达式替换成一个throw语句。

  1. var it2 = foo();
  2. console.log(it2.next())
  3. console.log(it2.throw(new Error('又错了!')))

return()是将yield表达式替换成一个return语句。

  1. var it3 = foo();
  2. console.log(it3.next())
  3. console.log(it3.return(666))

yield* 表达式

在一个Generator函数内部,调用另一个Generator函数,需要在前者的函数体内,手动完成遍历

  1. function* foo() {
  2. yield 1;
  3. yield 2;
  4. return 3;
  5. }
  6. function* bar() {
  7. yield 'x';
  8. for (let i of foo()) {
  9. console.log(i)
  10. }
  11. }
  12. for (let i of bar()) {
  13. console.log(i)
  14. }

如果在bar函数内部调用多个Generator函数,处理就会变得特别麻烦,所以出现了yield*表达式。

  1. // yield*版本
  2. function* bar2() {
  3. yield 'x'
  4. yield* foo()
  5. yield 'y'
  6. return 'zzz';
  7. }
  8. let it2 = bar2();
  9. console.log(it2.next())
  10. console.log(it2.next())
  11. console.log(it2.next())
  12. console.log(it2.next())
  13. console.log(it2.next())
  14. // 等同于
  15. function* bar3() {
  16. yield 'x';
  17. yield 1;
  18. yield 2
  19. yield 'y'
  20. return 'zzz'
  21. }
  22. console.log('\n')
  23. // 等同于
  24. function* bar4() {
  25. yield 'x';
  26. for (let v of foo()) {
  27. yield v
  28. }
  29. yield 'y'
  30. return 'zzz'
  31. }
  32. for (let v of bar4()) {
  33. console.log(v)
  34. }

再看下面的一个例子,outer2用了yield, outer1没用,结果是outer1返回了一个遍历器对象,outer2返回了遍历器对象内部的值。
从语法的角度看,如果yield后面跟着一个遍历器对象,那么需要在yield后加星号,表明它返回的是遍历器对象。这称为yield
表达式

  1. function* inner() {
  2. yield 'hello';
  3. }
  4. function* outer1() {
  5. yield 'open';
  6. yield inner()
  7. yield 'close';
  8. }
  9. var it1 = outer1();
  10. console.log(it1.next())
  11. console.log(it1.next())
  12. console.log(it1.next())
  13. console.log(it1.next())
  14. console.log(`\n outer2 `)
  15. function* outer2() {
  16. yield 'open';
  17. yield* inner()
  18. yield 'close'
  19. }
  20. var it2 = outer2();
  21. console.log(it2.next())
  22. console.log(it2.next())
  23. console.log(it2.next())
  24. console.log(it2.next())

再看下面这段代码,delegratingIterator是代理者,delegratedIterator是被代理者。由yield* delegratedIterator语句得到的是一个遍历器,所以要用星号表示。
运行结果就是使用了一个遍历器,遍历了多个Generator函数,有递归的效果。

  1. let delegratedIterator = (function* () {
  2. yield 'hello';
  3. yield 'bye'
  4. }())
  5. let delegratingIterator = (function* () {
  6. yield 'Greeting';
  7. yield* delegratedIterator;
  8. yield 'ok, bye'
  9. }())
  10. for (let value of delegratingIterator) {
  11. console.log(value)
  12. }

作为对象属性的Generator函数

  1. let obj = {
  2. *myGeneratorMethods() {
  3. yield 3
  4. yield 4
  5. return 5
  6. }
  7. }
  8. for (let value of obj.myGeneratorMethods()) {
  9. console.log(value)
  10. }
  11. console.log(`\n 另一种方式声明对象方法`)
  12. // 上面的写法等同于下面
  13. let obj2 = {
  14. myGeneratorMethods: function* () {
  15. yield 1;
  16. return 2;
  17. }
  18. }
  19. for (let value of obj2.myGeneratorMethods()) {
  20. console.log(value)
  21. }

Generator函数的this

Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例,也继承了Generator函数的prototype对象上的方法。

  1. function* foo() {
  2. }
  3. foo.prototype.hello = function () {
  4. return 'hello'
  5. }
  6. let obj = foo();
  7. console.log(obj instanceof foo)
  8. console.log(obj.hello())

上述代码表明,Generator函数foo返回的遍历器obj,是foo的实例,同时也继承了foo.prototype。

含义

Generator与状态机

  1. var ticking = true;
  2. var clock = function () {
  3. if (ticking) {
  4. console.log('tick')
  5. } else {
  6. console.log('tock')
  7. }
  8. ticking = !ticking
  9. }
  10. clock()
  11. clock()
  12. clock()
  13. // Generator版本
  14. function* clock2() {
  15. while (true) {
  16. console.log('tick')
  17. yield
  18. console.log('tock')
  19. yield
  20. }
  21. }
  22. var it = clock2();
  23. console.log(it.next())
  24. console.log(it.next())
  25. console.log(it.next())

Generator实现与ES5的实现相比,少了外部保存状态的ticking变量,更简洁、安全。Generator之所以不用保存状态,是因为它本身就包含了一个状态信息,
即目前是否出于暂停状态。