ES6新增特性,用于实现iterator(迭代器),是可以暂停执行的函数

  • Generator函数是一个状态机,内部封装了不同状态时的数据

  • 同时也是迭代器对象生成函数,可以一次遍历 Generator 函数内部的每一个状态

语法

  1. // 生成器函数声明
  2. function* generator () {
  3. yield '第一段'
  4. yield* '第二段'
  5. return '结束了'
  6. }
  7. // 生成器函数表达式
  8. let generatorFn = function* () {}
  9. // 作为对象字面量方法的生成器函数
  10. let foo = {
  11. * generatorFn() {}
  12. }
  13. // 作为类实例方法的生成器函数
  14. class Foo {
  15. * generatorFn() {}
  16. }
  17. // 作为类静态方法的生成器函数
  18. class Bar {
  19. static * generatorFn() {}
  20. }
  • function关键字后面,或函数名前加一个星号 *,即可定义Generator函数
    • 只要是可以定义函数的地方,都可以定义生成器
    • 箭头函数不能用来定义生成器函数
    • 星号两侧的空格不影响定义
  • Generator函数调用后并不会执行,而是会返回一个生成器对象,该对象实现了 Iterator。

  • 函数内部有一个 yield 关键字,用于定义 yield表达式

    • yield表达式用于定义不同的状态,是暂停标识
      const interator = generator()
      const { value, done } = interator.next(value)
      
  • Generator函数调用后不会执行代码,而是会返回一个迭代器对象(Iterator Object)

  • 通过调用迭代器对象的方法, 使得指针移向下一个状态,执行代码
  • 迭代器的next方法返回一个对象,包含value和done两个属性(iterator原理)
    • value:yield表达式的结果,return的值
    • done:是否遍历结束,暂停false,函数执行完成或return是true

next方法运行逻辑

  1. 遇到 yield表达式,就暂停函数的执行,并将 yield 后面的表达式的结果,作为返回对象的value值
  2. 下一次调用next时,从上一次暂停的 yield表达式 继续向后执行,直到遇到下一个yield
  3. 如果函数全部执行完成或遇到了return语句,就将return的值,作为value的值

注意点

  • 遇到yield表达式时,只会执行yield后面的表达式,该行语句中的其他代码都不会执行
  • 只有当下一次调用next,内部指针指向该行语句时,剩余的代码才会被执行

    function* gen() {
    const a = yield 123 - 456       // { value: -333, done: false }
    const b = (yield 123 + 456) + 1 // { value: 579, done: false }
    }
    
  • 上面代码中,执行到第一行时,会暂停执行,并获取 yield 后面的表达式 123 - 456 的结果

    • cont a = 则不会执行,直到下一次next时,才会被执行
  • 如果 yield表达式 使用括号了特别声明,那么暂停时,括号之外的也不会被执行
    • 如遇到第二行的yield时,只会执行 123 + 456,其他的等下一下next

next方法的参数

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

function* dataConsumer() {
    console.log('Started')
  console.log('1.' + ( yield ))
     console.log('2.' + ( yield ))
  return 'result'
}

let genObj = dataConsumer()
genObj.next()       // Started
genObj.next()            // 1.undefined
genObj.next('b')    // 2.b

for…of循环

for…of可以自动遍历Generator生成的遍历对象,不需要自己调用next方法

function* foo() {
  yield 1
  yield 2
  return 3
}

for (let v of foo()) {
  console.log(v)          // 1 2
}
  • 当next方法返回对象的done属性为true时,就会中止循环,且这次的返回对象不会参与循环
    • 上面代码中的6就没有打印
  • 每次循环会自动将返回对象中的value取出

遍历接口

原生JS对象没有 Iterator 接口,无法使用for..of,可以通过Generator 为它添加这个接口

function* objectEntries() {
  let propKeys = Object.keys(this)

  for (let propKey of propKeys) {
    yield [propKey, this[propKey]]
  }
}

let jane = { first: 'Jane', last: 'Doe' }
jane[Symbol.iterator] = objectEntries

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`)
}

其他语法

扩展运算符(…)、解构赋值和Array.from方法内部都是使用的遍历接口,因此Generator返回的遍历对象,可以作为参数

function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

// 扩展运算符
[...numbers()] // [1, 2]

// Array.from 方法
Array.from(numbers()) // [1, 2]

// 解构赋值
let [x, y] = numbers();
x // 1
y // 2

迭代器对象(interator)API

ES6规定返回的迭代器是 Generator 函数的实例,也继承了Generator的原型

Generator.prototype.throw()

迭代器对象有一个 throw方法,可以在函数体外抛出错误,并在函数体内进行捕获

function* g () {
    try {
      yield 'Started'
  } catch(e) {
      console.log('内部捕获', e)
  }
}

const i = g()
i.next()

try {
  i.throw(new Error(777));
  i.throw('a');
} catch (e) {
  console.log('外部捕获', e);
}
// 内部捕获 Error: 出错了!(…)
// 外部捕获 a
  • 调用该方法后,会在上一个yield处抛出错误,因此可以在函数体内进行捕获
    • 第一个被函数体内的catch捕获了
    • 第二个由于函数体内部的catch已经执行过了,无法再进行捕获,因此这个错误就被抛到了函数外部
  • throw方法可以接收一个参数,该参数就是错误信息,建议是一个 Error 实例
    • throw抛出的错误要被内部捕获,必须要调用一次next方法
    • 因为需要执行函数,让内部指向内try…catch包裹的yield表达式
    • 如果捕获了异常,代码会从catch继续向下执行
  • 如果Generator函数中的错误没有进行捕获,那么这个函数将结束遍历

Generator.prototype.return()

迭代器对象有一个 return方法,可以终结遍历

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next()        // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next()        // { value: undefined, done: true }
  • 调用后返回一个对象,同next
  • return() 可以接收一个参数,会成为返回对象的value的值,如果不传就是 undefined
  • 调用return后,返回对象的done为true,之后再调用next,done总是为true

next、throw、return共同点

next()throw()return()这三个方法本质上是做相同的事

  1. 使用不同语句替换上一个yield表达式
  2. 让Generator函数恢复执行 ```javascript const g = function* (x, y) { let result = yield x + y; return result; }

const gen = g(1, 2) gen.next(); // Object {value: 3, done: false}


`next()` 是将yield表达式替换成一个值
```javascript
gen.next(1); 
// 将 yield x + y 替换为1 
// let result = 1

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

gen.throw(new Error('出错了'))
// 将 yield x + y 替换为 throw new Error('出错了')
// let result = throw new Error('出错了')

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

gen.return(2)
// 将 yield x + y 替换为 return(2)
// // let result = return(2)