您现在的位置是:网站首页 > 迭代器模式(Iterator)与生成器函数的关系文章详情

迭代器模式(Iterator)与生成器函数的关系

迭代器模式是一种行为设计模式,它提供一种方法顺序访问聚合对象中的各个元素,而又不暴露其底层表示。生成器函数是JavaScript中实现迭代器的语法糖,两者在遍历数据集合时存在紧密的协同关系。

迭代器模式的核心概念

迭代器模式由两个核心部分组成:

  1. 可迭代对象(Iterable):实现[Symbol.iterator]()方法的对象
  2. 迭代器(Iterator):具有next()方法的对象,返回{value, done}结构
// 自定义迭代器示例
class Counter {
  constructor(limit) {
    this.limit = limit
  }

  [Symbol.iterator]() {
    let count = 1
    return {
      next: () => ({
        value: count,
        done: count++ > this.limit
      })
    }
  }
}

const counter = new Counter(3)
for (const num of counter) {
  console.log(num) // 1, 2, 3
}

生成器函数的本质

生成器函数(Generator Function)是ES6引入的特殊函数,通过function*语法定义。其执行会返回一个生成器对象,该对象同时符合可迭代协议和迭代器协议:

function* numberGen() {
  yield 1
  yield 2
  yield 3
}

const gen = numberGen()
console.log(gen.next()) // {value: 1, done: false}
console.log(gen[Symbol.iterator]() === gen) // true

生成器具有以下关键特性:

  • 通过yield暂停执行并返回值
  • 通过next()恢复执行
  • 自动实现迭代器接口

两者的协同工作机制

生成器函数本质上是对迭代器模式的语法封装。比较两种实现方式:

传统迭代器模式实现:

const range = {
  from: 1,
  to: 3,
  [Symbol.iterator]() {
    return {
      current: this.from,
      last: this.to,
      next() {
        return {
          value: this.current,
          done: this.current++ > this.last
        }
      }
    }
  }
}

生成器函数实现:

const range = {
  from: 1,
  to: 3,
  *[Symbol.iterator]() {
    for(let i = this.from; i <= this.to; i++) {
      yield i
    }
  }
}

生成器版本明显更简洁,因为:

  1. 自动维护迭代状态(current)
  2. 无需手动实现next()方法
  3. 控制流更直观

高级应用场景

惰性求值

生成器特别适合实现惰性计算:

function* fibonacci() {
  let [prev, curr] = [0, 1]
  while (true) {
    yield curr
    ;[prev, curr] = [curr, prev + curr]
  }
}

const fib = fibonacci()
console.log(fib.next().value) // 1
console.log(fib.next().value) // 1
console.log(fib.next().value) // 2

异步迭代

结合for await...of实现异步数据流处理:

async function* asyncGenerator() {
  const urls = ['/api/1', '/api/2']
  for (const url of urls) {
    const response = await fetch(url)
    yield response.json()
  }
}

(async () => {
  for await (const data of asyncGenerator()) {
    console.log(data)
  }
})()

性能考量

虽然生成器提供了更优雅的语法,但在性能敏感场景需要注意:

  • 生成器对象创建开销比普通迭代器略高
  • V8引擎对生成器的优化不如常规循环
  • 大量数据遍历时可能产生内存压力
// 性能对比基准测试
function testGenerator(n) {
  function* gen() {
    for (let i = 0; i < n; i++) yield i
  }
  console.time('generator')
  for (const _ of gen()) {}
  console.timeEnd('generator')
}

function testIterator(n) {
  const iter = {
    [Symbol.iterator]() {
      let i = 0
      return {
        next: () => ({ value: i, done: i++ >= n })
      }
    }
  }
  console.time('iterator')
  for (const _ of iter) {}
  console.timeEnd('iterator')
}

testGenerator(1e6)
testIterator(1e6)

设计模式扩展应用

迭代器模式可以与其他模式结合:

组合模式+迭代器:

class TreeNode {
  constructor(value) {
    this.value = value
    this.children = []
  }

  *[Symbol.iterator]() {
    yield this.value
    for (const child of this.children) {
      yield* child
    }
  }
}

const tree = new TreeNode('root')
tree.children.push(new TreeNode('child1'))
tree.children[0].children.push(new TreeNode('grandchild'))
console.log([...tree]) // ['root', 'child1', 'grandchild']

观察者模式+迭代器:

function* createEventStream(target, eventType) {
  let callback
  target.addEventListener(eventType, e => {
    if (callback) callback(e)
  })
  while (true) {
    yield new Promise(resolve => callback = resolve)
  }
}

const button = document.querySelector('button')
const clickStream = createEventStream(button, 'click')
;(async () => {
  for await (const event of clickStream) {
    console.log('Button clicked', event)
  }
})()

ECMAScript规范的演进

ES2018引入的异步迭代器协议进一步完善了生成器的能力:

const asyncIterable = {
  [Symbol.asyncIterator]() {
    let i = 0
    return {
      next() {
        return new Promise(resolve => {
          setTimeout(() => {
            resolve({ value: i++, done: i > 3 })
          }, 1000)
        })
      }
    }
  }
}

;(async () => {
  for await (const num of asyncIterable) {
    console.log(num) // 0, 1, 2 (每秒输出一个)
  }
})()

实际工程中的取舍

在真实项目中选择实现方式时需要考虑:

  • 代码可读性与维护成本
  • 团队成员的熟悉程度
  • 运行环境对ES6特性的支持
  • 性能瓶颈的实际影响

对于简单的遍历需求,直接使用数组方法可能更合适;对于复杂的数据结构或需要惰性求值的场景,生成器往往能提供更清晰的抽象。

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步