【记一忘三二】迭代器和生成器

66 阅读3分钟

迭代器

介绍

迭代器是一种设计模式,通过可迭代对象的提供迭代接口获取迭代对象,依次执行迭代对象提供的方法输出值

可迭代对象其特征如下:

  • 有序性:元素具有明确的顺序
  • 连续性:元素可以按顺序连续访问

以下类型是可迭代对象

  • Array(数组)
  • Map(键值对映射)
  • Set(集合)
  • NodeList(DOM 节点列表)
  • TypedArray(类型化数组)
  • arguments(函数的参数对象)

Symbol.iterator

可迭代对象上存在Symbol.iterator方法,调用Symbol.iterator方法可以得到一个迭代器*,在通过调用迭代器next()方法可以得到一个迭代结果,包含两个属性: 迭代结果是一个对象,包含两个属性:

  • value:当前迭代到的值
  • done:布尔值,表示迭代是否结束
// 定义可迭代对象
const arr = [1, 2, 3]

// 获取迭代对象
const arrIterator = arr[Symbol.iterator]()

// 执行迭代器
arrIterator.next()  // {value: 1, done: false}
arrIterator.next()  // {value: 2, done: false}
arrIterator.next()  // {value: 3, done: false}
arrIterator.next()  // {value: undefined, done: true}

实现原理

以下是实现一个类似Symbol.iterator方法

Symbol.iterator = Symbol('Symbol.iterator')
Object.defineProperty(Array.prototype, Symbol.iterator, {
  configurable: false,
  enumerable: false,
  writable: false,
  value() {
    let index = 0
    const data = this
    return {
      next() {
        if (index < data.length) {
          return {
            value: data[index++],
            done: false,
          }
        }
        else {
          return {
            value: undefined,
          }
        }
      },
    }
  },
})

不同的迭代器

每个可迭代对象不仅提供Symbol.iterator方法,还提供了其他方法(如 keysvaluesentries),这些方法也返回迭代器对象 这些方法的具体功能如下:

  • keys():返回一个迭代器,输出数组的键(索引)。
  • values():返回一个迭代器,输出数组的值。
  • entries():返回一个迭代器,输出数组的键值对(索引和值的组合)。
// 定义可迭代对象
const arr = [1, 2, 3]

// 获取迭代器对象
const arrIterator = arr[Symbol.iterator]()
const arrKeysIterator = arr.keys()
const arrValuesIterator = arr.values()
const arrEntriesIterator = arr.entries()

// 执行迭代器
arrIterator.next() // { value: 1, done: false }
arrKeysIterator.next() // { value: 0, done: false }
arrValuesIterator.next() // { value: 1, done: false }
arrEntriesIterator.next() // { value: [ 0, 1 ], done: false }

可迭代对象中,Symbol.iterator方法实际上是 keysvalues entries 方法中的其中之一

image-20250122165115534

对象不迭代性

// 定义可迭代对象
const obj = { a: 1, b: 2, c: 3 }

// 获取迭代器对象
const objIterator = obj[Symbol.iterator]() //  obj[Symbol.iterator] is not a function

// 输出Object的迭代器接口
Object.prototype[Symbol.iterator]  // undefined

obj对象没有迭代器接口,也就无法生成迭代器对象

注:这是因为Object对象的属性没有明确的顺序

如果需要使Object对象可迭代,可以通过手动实现Symbol.iterator方法来实现

// 定义可迭代对象
const obj = { a: 1, b: 2, c: 3 }

obj[Symbol.iterator] = function () {
  const keys = Object.keys(this)
  let index = 0

  return {
    next: () => {
      if (index < keys.length) {
        return {
          value: {
            key: keys[index],
            value: this[keys[index++]],
          },
          done: false,
        }
      }
      else {
        return {
          value: undefined,
          done: true,
        }
      }
    },
  }
}

// 获取迭代器对象
const objIterator = obj[Symbol.iterator]()

// 执行迭代器
objIterator.next() // { value: { key: 'a', value: 1 }, done: false }
objIterator.next() // { value: { key: 'b', value: 2 }, done: false }
objIterator.next() // { value: { key: 'c', value: 3 }, done: false }
objIterator.next() // { value: undefined, done: true }

使用

手动迭代

这种方式并不常用,因为手动执行迭代器的次数是未知的,且需要手动检查 done 属性以判断迭代是否完成。

// 定义可迭代对象
const set = new Set([1, 2, 3])

// 获取迭代对象
const setIterator = set[Symbol.iterator]()

// 执行迭代器
console.log(setIterator.next()) // {value: 1, done: false}

循环迭代

循环迭代避免了迭代器对象执行执行次数未知的问题,但是任然需要手动判断迭代是否结束

// 定义可迭代对象
const set = new Set([1, 2, 3])

// 获取迭代对象
const setIterator = set[Symbol.iterator]()

// 执行迭代器
while (true) {
  const { value, done } = setIterator.next()
  if (done)
    break
  console.log(value)
}

在ES6中,提供了for...of循环,会自动调用被循环的对象的Symbol.iterator方法返回一个迭代器对象,然后依次调用迭代器对象next方法参与循环体的循环

// 定义可迭代对象
const set = new Set([1, 2, 3]);

// 使用循环迭代
for (const item of set) {
  console.log(item); 
}

扩展运算符

这也是ES6新增的语法,使用扩展运算符...可以展开一个可迭代对象,返回一个数组 内部是通过调用Symbol.iterator方法获取被展开对象的迭代器,后依次调用被展开对象next方法输出展开对象中的值数组

const arr = [1, 2, 3]

const copyArr2 = [...arr]