Skip to content

ES6学习:迭代器和生成器

迭代协议

迭代器本身并不那么有趣,但它却可以支持那些有趣的行为。迭代器协议让任何对象变得可迭代

js
class Log {
  constructor() {
    this.messages = [];
  }
  add(message) {
    this.messages.push({
      message,
      timestrmp: Date.now()
    })
  }
}

若想对log记录进行迭代呢? 只要访问log.messages就行,不过如果log可以像数组一样直接迭代就更好了,迭代器协议就是用来实现这个的。

迭代器协议是说:如果一个类提供了一个符号方法Symbol.iterator,这个方法返回一个具有迭代行为的对象(比如:对象有next方法,同时next方法返回一个包含value和done的对象),那么这个类就是可迭代的!接着修改以上代码:

js
class Log {
  constructor() {
    this.messages = [];
  }
  add(message) {
    this.messages.push({
      message,
      timestamp: Date.now()
    })
  }
  [Symbol.iterator]() {
    return this.messages.values();
  }
}

下面就可以像数组那样迭代Log类的实例了:

js
const log = new Log();
log.add('first');
log.add('second');
log.add('third');
...

// 像数组一样迭代log
for (let item of log) {
  console.log(`${item.message} @ ${item.timestamp}`)
}

在这个例子中,通过从mesages数组中取出一个迭代器的方式保持迭代器协议,当然也可以编写自己的迭代器,来看下面:

js
class Log {
  constructor() {
    this.messages = [];
  }
  add(message) {
    this.messages.push({
      message,
      timestamp: Date.now()
    })
  }
  [Symbol.iterator]() {
    let i = 0;
    const messsages = this.messages;
    return {
      next() {
        if (i >= messages.length) {
          return {
            value: undefined,
            done: true
          }
        }
        return {
          value: messages[i++],
          done: false
        }
      }
    }
  }
}

使用迭代器的理想应用: 斐波那契数列 它与上面例子的最大区别在于,它的done值永远不会为true

js
class FibonacciSequence {
  [Symbol.iterator]() {
    let a = 0, b = 1;
    return {
      next() {
        let rval = { value: b, done: false }
        b += a;
        a = rval.value;
        return rval;
      }
    }
  }
}

生成器

生成器是使用迭代器来控制其运行的函数。一般来说,函数会获取参数然后返回结果,但是函数调用者并没办法控制该函数。当调用一个函数的时候,实际上是放弃了对函数的控制,直到函数返回。有了生成器就可以在函数执行时对它进行控制。

生成器提供两种能力:首先,是控制函数执行的能力,使函数能够分步执行;其次,是与执行中的函数对话的能力。

生成器与一般的函数有两种不同的地方:

  • 函数可以通过使用域 yield,在其运行的任意时刻将控制权返给调用方
  • 调用生成器时,它并不是立即执行,而是会回到迭代器中,函数会在调用迭代器的next方法时执行

yield表达式和双向交流

生成器可以让它和其调用者进行双向交流,该功能是通过yield表达式实现的。表达式可以计算出值,而yield也是一个表达式,所以它一定可以计算出一个值。它计算的是调用方每次在生成器的迭代器上调用next时提供的参数(如果有的话)

js
function* interrogate() {
  const name = yield 'What is your name?';
  const color = yield 'what is your favourite color?';
  return `${name}'s favourite color is ${color}.`
}

生成器和返回值

yield表达式本身并不能让生成器结束,即时它是生成器的最后一个语句。在生成器的任何位置调用return 都会使done的值变为true, 而value的值则是任何被返回的值

所以建议不要在return中提供一个对生成器有意义的值。 在生成器调用return时,不提供返回值。

总结

迭代器为集合或对象这类可以提供多个值的数据类型提供了一个标准的方式。而迭代器在ES6出现之前是不提供任何东西的,它们只是标准化那些重要且常见的活动。

生成器使得函数更易于控制和定制化:函数调用方不再局限于提供数据后等待函数返回,从而获取函数返回值。生成器实际上是将计算延迟了,只需要的时候进行。

共 20 个模块,1301 篇 Markdown 文档。