JavaScript迭代器与迭代接口详解:原理、自定义与原生应用

作者:服务小站时间:2026-10-25 08:23:54浏览:101

本文详细解析JavaScript中迭代器与迭代接口的核心机制。通过[Symbol.iterator]工厂函数创建迭代器,利用.next()方法控制遍历状态。文章演示如何为任意对象实现可迭代协议,并深入剖析for-of循环、数组解构等原生语法背后的迭代逻辑,以及迭代器一次性与不可逆的特性。

1,迭代器的定义

所有可迭代对象均关联一个可迭代接口[Symbol.iterator];

[Symbol.iterator]接口并非迭代器本身,而是迭代器工厂函数,调用该接口可返回处于待执行状态的迭代器;

各类原生全局对象均对应特定的迭代器类型;

const arr = new Array()
const map = new Map()
const set = new Set()
 
console.log(arr[Symbol.iterator]()) //Array Iterator {}
console.log(map[Symbol.iterator]()) //MapIterator {}
console.log(set[Symbol.iterator]()) //SetIterator {}

通过调用迭代器对象的 .next() 方法,可将状态从待执行转为真正执行,其返回值即为 next() 方法返回的对象;

const iterator = new Array(1, 2, 3, 4)[Symbol.iterator]()
 
console.log(iterator.next()) //{value: 1, done: false}
console.log(iterator.next()) //{value: 2, done: false}
console.log(iterator.next()) //{value: 3, done: false}
console.log(iterator.next()) //{value: 4, done: false}
console.log(iterator.next()) //{value: undefined, done: true}

当执行第四次调用时,对应 arr[3],此时返回对象的 done 属性仍为 false;而执行第五次时,value 变为 undefined,done 变为 true,为何会出现此现象?

在解答上述问题前,需重新审视迭代器的概念:

从本质上看,迭代器对象即为实现了 next() 方法的对象

const myIterator = {
  next() {
    if (length) {
      return { value: 1, done: false }
    } else {
      return { value: undefined, done: true }
    }
  },
}

如上所示,这是一个最基础的迭代器示例。调用 next() 会执行迭代逻辑,返回 done 为 false 的生成对象,直至满足特定条件,返回 done 为 true 的生成对象。

可将 Array 迭代器的原理简化理解为如下形式:

const myIterator = {
  next() {
    if (length) {
      return { value: 1, done: false }
    } else {
      return { value: undefined, done: true }
    }
  },
}

其输出结果与预期一致:

const iterator = new MyArray(1, 2, 3, 4)[Symbol.iterator]() console.log(iterator.next()) //{value: 1, done: false} console.log(iterator.next()) //{value: 2, done: false} console.log(iterator.next()) //{value: 3, done: false} console.log(iterator.next()) //{value: 4, done: false} console.log(iterator.next()) //{value: undefined, done: true}

至此,前述问题便迎刃而解。

或许你已注意到,我自定义了一个 MyArray 类,并手动重写了其迭代器接口。那么,是否只需为不可迭代对象实现 [Symbol.iterator],即可将其转化为可迭代对象?

答案肯定。只要有意愿,即可为任意对象添加可迭代协议,使其成为可迭代对象。因为可迭代对象的定义即为:实现了迭代接口的对象。

2,自定义迭代接口

基于上述思路,我们可以手动实现一个可迭代的 Object 对象:

const prototype = {
  [Symbol.iterator]() {
    const entries = Object.entries(this)
    const { length } = entries
    let index = 0
    return {
      next() {
        return index < length
          ? {
              value: {
                key: entries[index][0],
                value: entries[index++][1],
              },
              done: false,
            }
          : { value: undefined, done: true }
      },
    }
  },
}
 
const obj = Object.create(prototype)
obj.name = 'zhang san'
obj.age = 28
 
const objIterator = obj[Symbol.iterator]()
console.log(objIterator.next()) //{value: {key:'name',value:'zhang san'}, done: false}
console.log(objIterator.next()) //{value: {key:'age',value:28}, done: false}
console.log(objIterator.next()) //{value: undefined, done: true}

首先,声明一个重写了迭代接口的对象,随后使用 Object.create() 创建以该对象为原型的 obj。

该对象实例自身不包含迭代接口,但会沿原型链查找 prototype 对象是否存在迭代接口。只要原型链上存在迭代接口,即代表其为可迭代对象。例如:

const objSon = Object.create(obj, {
  name: {
    enumerable: true,
    writable: true,
    value: 'zhang xiao san',
  },
  age: {
    enumerable: true,
    writable: false,
    value: 2,
  },
  secret: {
    enumerable: false,
    writable: true,
    value: 'secret',
  },
})
const sonIterator = objSon[Symbol.iterator]()
 
console.log(sonIterator.next()) //{value: {key:'name',value:'zhang xiao san'}, done: false}
console.log(sonIterator.next()) //{value: {key:'age',value:2}, done: false}
console.log(sonIterator.next()) //{value: undefined, done: true}

objSon 依然是一个可迭代对象。

现在,我们通过改造迭代接口 [Symbol.iterator] 的方式,将原本非迭代类型的对象转化为可迭代对象。

我们可以利用该迭代接口遍历所有 enumerable 属性。若希望将 enumerable 为 false 的 secret 属性也遍历出来,只需改造迭代接口中的 entries 逻辑即可,一切由你掌控:

// const entries = Object.entries(this)
 
    const ownKeys = Object.getOwnPropertyNames(this)
    const entries = ownKeys.reduce(
      (result, key) => [...result, [key, this[key]]],
      []
    )

3,原生语言的迭代机制

以 for - of 为例进行说明

for (const item of obj) {
  console.log(item)
}
//{key:'name',value:'zhang san'}
//{key:'age',value:28}
 
for (const item of objSon) {
  console.log(item)
}
//{key:'name',value:'zhang xiao san'}
//{key:'age',value:2}

可见,无论是 obj 还是 objSon,均可正常使用 for of 循环,且返回值为迭代器生成对象 value 属性的值。

可如此理解 for - of 的机制:后台调用提供的可迭代对象的工厂函数 [Symbol.iterator],从而创建迭代器,然后自动调用迭代器 next 执行。若 done 为 false,则将迭代器生成对象 value 赋值给 item;若 done 为 true,则跳出循环:

const objIterator = obj[Symbol.iterator]()
{
  const { value: item,done } = objIterator.next()
  if(done) break
  console.log(item) //{key:'name',value:'zhang san'}
}
{
  const { value: item,done } = objIterator.next()
  if(done) break
  console.log(item) //{key:'age',value:28}
}
{
  const { value: item,done } = objIterator.next()
  if(done) break
  console.log(item) 
}

不仅限于 for - of,原生语言的迭代机制均与此类似。数组解构、拓展操作符、Array.from、new Set()、new Map()、Promise.all()、Promise.race()、yield * 操作符等,均属于原生迭代语言范畴。

了解 for - of 循环机制后,请观察以下示例:

const arr = [1, 2, 3, 4, 5, 6, 7, 8]
for (const item of arr) {
  console.log(item)
  if (item > 3) break
}
for (const item of arr) {
  console.log(item)
}
//输出结果:1,2,3,4 | 1,2,3,4,5,6,7,8
 
const arrIterator = arr[Symbol.iterator]()
for (const item of arrIterator) {
  console.log(item)
  if (item > 3) break
}
for (const item of arrIterator) {
  console.log(item)
}
//输出结果:1,2,3,4 | 5,6,7,8

循环 arr 的输出结果与循环 arr 迭代器 arrIterator 的结果存在明显差异。为何会出现这种现象?

在迭代器对象中,还有一个重要知识点:迭代器对象是一次性且不可逆的。

因此,若在迭代中途终止,只能从上次终止的位置继续执行,而无法从头开始。

那么,为何对 arr 本身使用 for - of 时,却能从头开始执行?可回顾介绍 for - of 循环机制的部分,其中提到:后台调用提供的可迭代对象的工厂函数 [Symbol.iterator],从而创建一个迭代器。

这意味着,每次调用 for - of 循环,都会创建一个新的迭代器对象,而该迭代器对象在循环结束时将被作为垃圾对象回收。

虽然 arr 连续调用了两次 for - of 循环,但在循环体内部,并非使用同一个迭代器对象。因此,即使上一个迭代器在 item>3 条件处中止,下一次循环的迭代器对象也是一个全新且未执行过的实例。

那么,为何调用 arr[Symbol.iterator] 接口生成的迭代器对象 arrIterator,会出现继续上次执行的情况?换言之,为何 arrIterator 的两次 for 循环未产生两个迭代器对象?

实际上,迭代器对象本身也实现了迭代器接口。也就是说,arr 拥有迭代器接口 [Symbol.iterator],而迭代器对象 arrIterator 也拥有迭代器接口 [Symbol.iterator],并且,调用该迭代器对象会返回其本身:

console.log(arrIterator[Symbol.iterator]() === arrIterator) // true

因此,虽然每次执行 for-of 循环都会调用迭代器接口,但该接口返回的对象即为 arr 的迭代器对象本身。由于迭代器对象是一次性且不可逆的,上述现象便显而易见。

细心的读者可能会提出一个问题:我们在前文手动实现的可迭代对象,其迭代器对象是否支持迭代接口?答案是:不支持。

for (const item of objIterator) {
  console.log(item)
}
//Uncaught TypeError: objIterator is not iterable

但其实优化也很简单,只需将该迭代器对象也实现一个迭代接口,且该迭代接口工厂函数返回其本身即可。

const prototype = {
  [Symbol.iterator]() {
    // const entries = Object.entries(this)
 
    const ownKeys = Object.getOwnPropertyNames(this)
    const entries = ownKeys.reduce(
      (result, key) => [...result, [key, this[key]]],
      []
    )
    const { length } = entries
    let index = 0
    return {
      next() {
        return index < length
          ? {
              value: {
                key: entries[index][0],
                value: entries[index++][1],
              },
              done: false,
            }
          : { value: undefined, done: true }
      },
      [Symbol.iterator]() {
        return this
      },
    }
  },
}
 
const objIterator = obj[Symbol.iterator]()
 
for (const item of objIterator) {
  console.log(item)
  if (item.key === 'name') break
}
for (const item of objIterator) {
  console.log(item)
}
// {key: 'name', value: 'zhang san'}
// {key: 'age', value: 28}

现在,你可以运用文中介绍的方法,将任何对象转化为可迭代对象。

总结

本文到此结束,希望能对你有所帮助,也欢迎继续关注本站的更多内容!

作者最新文章