for...of 是专为可迭代对象设计的语法糖,要求对象实现 Symbol.iterator 方法并返回合法迭代器;普通对象默认不可迭代,需手动实现;常见可迭代对象包括 Array、String、Set、Map、TypedArray、arguments 和 NodeList。

for...of 循环不是“遍历数组的另一种写法”,它是专门面向可迭代对象(iterable) 的语法糖——只要一个对象实现了 Symbol.iterator 方法并返回合法迭代器,它就能被 for...of 消费。普通对象(如 {a: 1, b: 2})默认不满足这个条件,直接用会报 TypeError: xxx is not iterable。
哪些值能直接用 for...of?
常见可迭代对象包括:Array、String、Set、Map、TypedArray、arguments、NodeList(如 document.querySelectorAll() 返回值)。它们原生支持迭代协议。
-
Array:按索引顺序取值 -
String:按 Unicode 码点逐字符遍历(注意:代理对如 emoji 可能被拆开) -
Map:每次迭代返回[key, value]数组,需用解构for (const [k, v] of map) -
Set:只返回元素值,无重复、无索引 -
NodeList:可直接遍历 DOM 节点,无需转成数组
const list = document.querySelectorAll('p');
for (const p of list) {
p.style.color = 'blue';
}想让普通对象也能 for...of 怎么办?
必须手动实现 Symbol.iterator。这不是“加个方法就行”,它得返回一个符合规范的迭代器对象(即有 next() 方法,且返回 { value, done } 形式)。
- 不能只写
obj[Symbol.iterator] = () => ({})—— 缺少next会报错 - 若只想遍历自身可枚举属性值,可用
Object.values(this)构造迭代器 - 注意:
for...in遍历的是键名,for...of遍历的是你定义的value,二者语义完全不同
const obj = { a: 1, b: 2 };
obj[Symbol.iterator] = function* () {
for (const key of Object.keys(this)) {
yield this[key];
}
};
for (const val of obj) console.log(val); // 1, 2
为什么不用 for...in 替代?
for...in 是为遍历对象属性设计的,它会:
- 遍历所有可枚举属性(包括原型链上的)
- 不保证顺序(尤其在老引擎中)
- 返回的是键名(
string),不是值
for...of 的目标是**数据消费**,强调顺序、可控性与一致性。比如遍历 Map 时,for...in 根本不会进入循环(因为 Map 不是普通对象,没有可枚举属性),但 for...of 天然支持。更关键的是:当你写 for (const item of arr),你明确表达“我要处理每个元素”;而 for (const i in arr) 其实是在说“我要检查每个索引键”,语义错位容易引发 bug(比如误把数组方法当元素)。
立即学习“Java免费学习笔记(深入)”;
常见翻车点和替代方案
遇到 TypeError: xxx is not iterable 别急着查文档,先确认三件事:
- 你操作的对象是否真的实现了
Symbol.iterator?typeof xxx[Symbol.iterator]应为"function" - 是否误把
undefined或null当作可迭代对象传入? - 是否在异步场景下用了同步
for...of?此时应改用for await...of(如遍历AsyncIterator)
如果只是想带索引遍历数组,别手写计数器,优先用:arr.entries():
const arr = ['a', 'b', 'c'];
for (const [i, val] of arr.entries()) {
console.log(i, val); // 0 'a', 1 'b', 2 'c'
}真正难的不是语法,而是判断“这个东西到底算不算可迭代”——它取决于协议,不是类型。很多开发者卡在这里,是因为把 for...of 当成“高级 for 循环”,其实它是数据契约的体现:你提供迭代器,我负责消费。











