什么是javascript的迭代器和可迭代协议_for-of循环如何工作

JavaScript迭代器必须实现可调用的next()方法,返回{value, done}对象;可迭代对象需部署Symbol.iterator方法返回迭代器;for-of仅依赖该协议,不推测结构,错误即报错。

JavaScript 迭代器对象必须实现 next() 方法

迭代器是一个有状态的对象,核心是它必须带一个 next() 方法,每次调用返回形如 { value: any, done: boolean } 的对象。只要满足这个结构,哪怕手动构造,JS 就认它是迭代器。

常见错误是以为“有 next 属性就行”,但漏掉可调用性——next 必须是函数,否则 for-of 会报 TypeError: iterator.next is not a function

  • done: true 后再调用 next(),仍应返回 { value: undefined, done: true }(规范要求)
  • 迭代器通常不重用:同一个迭代器对象多次用于 for-of,第二次会从上次暂停处继续,而非重头开始
  • 手写时别忘了闭包保存内部状态(比如数组索引),否则多次调用会混乱

Symbol.iterator 是可迭代协议的入口点

一个对象要被 for-of、解构、Array.from() 等识别为“可迭代的”,必须在其原型链上提供 Symbol.iterator 方法,且该方法返回一个迭代器对象。

内置类型如 ArrayStringMapSet 都实现了它;普通对象默认没有,所以 for-of 直接遍历 plain object 会报错 TypeError: XXX is not iterable

  • 自定义类可通过在原型或实例上定义 [Symbol.iterator]() { return ... } 来支持迭代
  • 箭头函数不能用作 Symbol.iterator 方法,因为它没有 this 绑定,无法访问实例数据
  • 如果 Symbol.iterator 返回的不是迭代器(比如返回数字或 null),for-of 会立即抛 TypeError

for-of 循环底层只做三件事

for-of 不关心数据结构是什么,只按固定流程执行:取迭代器 → 反复调用 next() → 检查 done → 提取 value。它不依赖 length、下标或键名。

const arr = [1, 2, 3];
const it = arr[Symbol.iterator]();
let result;
while (!(result = it.next()).done) {
  console.log(result.value); // 依次输出 1, 2, 3
}
  • 每次循环体执行前,自动调用一次 next();若 donetrue,循环终止
  • breakreturn 退出时,引擎**不会**自动调用 return()(即使迭代器有),这是容易被忽略的资源清理盲区
  • 异步迭代器(Symbol.asyncIterator)需用 for-await-of,混用会静默失败或报错

字符串和类数组对象的迭代行为差异

字符串按 Unicode 码点迭代(正确处理 emoji 和代理对),而 argumentsNodeList 等类数组对象虽有 length 和数字键,但默认不可迭代——除非显式部署 Symbol.iterator(现代 DOM API 已补全 NodeList.prototype[Symbol.iterator])。

常见陷阱:用 Array.from(someNodeList) 能工作,是因为它内部检测到 Symbol.iterator;但直接 for-of someNodeList 在旧环境会失败。

  • IE 完全不支持 Symbol.iterator,需用 for 循环 + length 回退
  • Array.from({ length: 3 }) 返回 [undefined, undefined, undefined],因为该对象无 Symbol.iteratorArray.from 改用 length + undefined 填充逻辑
  • 手动让类数组可迭代,最简方式:obj[Symbol.iterator] = Array.prototype[Symbol.iterator];(前提是它有合法的 length 和索引属性)
真正难的不是写出一个能跑的迭代器,而是理解 for-of 对协议的严格依赖——它既不推测结构,也不容错兜底。一旦 Symbol.iterator 缺失或 next() 返回格式错,就立刻崩,没商量。