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, 2for...in 替代?for...in 是为遍历对象属性设计的,它会:
string),不是值for...of 的目标是**数据消费**,强调顺序、可控性与一致性。比如遍历 Map 时,for...in 根本不会进入循环(因为 Map 不是普通对象,没有可枚举属性),但 for...of 天然支持。更关键的是:当你写 for (const item of arr),你明确表达“我要处理每个元素”;而 for (const i in arr) 其实是在说“我要检查每个索引键”,语义错位容易引发 bug(比如误把数组方法当元素)。
遇到 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 循环”,其实它是数据契约的体现:你提供迭代器,我负责消费。