← 返回题目列表

Iterator 和 for...of 的关系是什么?

高频 中等 第 12 / 28 题 更新于 2026/07/28
ES6Iteratorfor...of

简化版

Iterator 是统一遍历协议,for...of 会读取对象的 Symbol.iterator 方法,拿到迭代器后不断调用 next(),直到 { done: true } 结束。

详细版

可迭代对象必须实现 Symbol.iterator 方法。 这个方法返回一个迭代器对象,迭代器对象需要有 next() 方法。

const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
iterator.next(); // { value: 1, done: false }

for...of 消费的是迭代协议,不是下标。 数组、字符串、Map、Set、arguments、NodeList 等都可以被遍历,是因为它们实现了迭代协议。 普通对象默认不能 for...of,除非手动实现 Symbol.iterator

完整版教学

一、Iterator 的目标是统一遍历入口

ES6 之前,不同数据结构的遍历方式不统一。 数组用下标,普通对象用 for...inObject.keys,类数组对象又要借方法转换。 Iterator 协议提供一个共同抽象:只要对象能按约定吐出下一个值,它就可以被统一消费。

数据结构
  -> Symbol.iterator()
  -> iterator.next()
  -> { value, done }

这个抽象让 for...of、展开运算符、解构赋值、Array.from 都能复用同一套遍历能力。 所以它不是单独服务于循环语法,而是 ES6 数据消费模型的一部分。

二、可迭代对象和迭代器不是同一个概念

可迭代对象是拥有 Symbol.iterator 方法的对象。 迭代器是拥有 next() 方法、能一步步返回结果的对象。 数组本身是可迭代对象,但数组本身不是它自己的迭代器。

const arr = [10, 20];

console.log(typeof arr[Symbol.iterator]); // "function"

const it = arr[Symbol.iterator]();
console.log(it.next()); // { value: 10, done: false }
console.log(it.next()); // { value: 20, done: false }
console.log(it.next()); // { value: undefined, done: true }

用数字例子看更清楚:长度为 2 的数组通常会经历 3 次 next()。 前 2 次给值,第 3 次告诉消费者结束。

三、for…of 背后就是不断调用 next

for...of 的语义可以近似理解成:先取迭代器,再循环调用 next()。 只要 done 不是 true,就把 value 交给循环变量。 当 donetrue,循环结束。

for (const item of [1, 2, 3]) {
  console.log(item);
}

近似流程:

取 iterable[Symbol.iterator]()
  -> next() 得到 1
  -> next() 得到 2
  -> next() 得到 3
  -> next() done=true
  -> 结束

这解释了为什么 for...of 可以遍历 Set。 Set 没有数字下标,但它实现了迭代协议。

四、for…of 和 for…in 的差别很高频

for...in 遍历的是对象可枚举属性名,常用于对象属性枚举。 for...of 遍历的是可迭代对象吐出的值,常用于数组、字符串、Map、Set。 把两者混用,是面试和项目里都常见的问题。

维度for...infor...of
关注点属性名 key迭代值 value
依赖机制可枚举属性Iterator 协议
数组结果下标字符串元素值
普通对象默认可用默认不可用
const arr = ['a', 'b'];

for (const k in arr) console.log(k); // "0", "1"
for (const v of arr) console.log(v); // "a", "b"

数组遍历业务值时优先考虑 for...of。 枚举对象属性时再考虑 for...in,并注意原型链属性过滤。

五、自定义可迭代对象怎么写

普通对象默认不能 for...of,但可以通过实现 Symbol.iterator 变成可迭代对象。 关键是返回一个带 next() 的对象。 每次 next() 都要返回 { value, done }

const range = {
  start: 1,
  end: 3,
  [Symbol.iterator]() {
    let current = this.start;
    const end = this.end;
    return {
      next() {
        if (current <= end) {
          return { value: current++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

console.log([...range]); // [1, 2, 3]

这个 range 对象没有数组下标,却能被展开运算符消费。 原因不是展开运算符认识 range,而是它认识迭代协议。

六、Generator 是创建迭代器的语法糖

手写 next() 比较繁琐。 Generator 函数能自动返回一个迭代器,每次 yield 对应一次可产出的值。 所以 Generator 常被用来实现自定义遍历。

function* range(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;
  }
}

console.log([...range(1, 3)]); // [1, 2, 3]

记忆钩子:for...of 不关心你是不是数组,它只问你有没有 Symbol.iterator

从工程角度看,自定义迭代适合“按需产生值”的场景。 例如分页数据、树结构遍历、流式解析都可以用迭代协议隐藏内部细节。

七、常见误区与追问

  • 误区:能用下标访问就一定能 for...of for...of 看的是迭代协议,不是下标能力;类数组对象也要看是否实现 Symbol.iterator
  • 误区:普通对象可以直接 for...of 普通对象默认没有 Symbol.iterator,需要手动实现或转成 Object.keys/values/entries
  • 误区:for...infor...of 都适合遍历数组值。 for...in 得到的是属性名,数组业务值更适合 for...of
  • 追问:展开运算符为什么能展开 Set? Set 实现了迭代协议,展开运算符会读取它的迭代器并逐个取值。
  • 追问:Iterator 的 done 有什么作用? 它告诉消费者遍历是否结束;没有结束信号,循环就无法可靠停止。
  • 追问:Generator 和 Iterator 是什么关系? Generator 返回的对象符合迭代器协议,是写迭代器的一种简洁方式。

八、加强记忆

把 Iterator 理解成“统一取下一个值的接口”:可迭代对象提供 Symbol.iterator,迭代器提供 next()for...of 负责循环消费。答题时用数组的 3 次 next() 例子解释协议,再对比 for...in,基本就能覆盖主要追问。