for...in遍历对象属性名(含原型链可枚举属性),for...of遍历可迭代对象的值(需Symbol.iterator)。前者专为对象设计,后者专为可迭代数据设计,目标对象与遍历内容均不可互换。

核心就一句话:for...in 遍历的是“名字”,for...of 遍历的是“东西本身”。不是风格差异,是设计目的完全不同——一个专为对象而生,一个专为可迭代数据而设。
看目标对象:谁该用谁,根本没得商量
for...in 的合法目标只有对象(包括数组,但那是历史包袱,不推荐)。它会把对象当“属性集合”来扫,不管你是 {name: "张三", age: 25} 还是 [1, 2, 3],它只关心“有哪些键”:
– 对象 → 输出 "name"、"age"
– 数组 → 输出 "0"、"1"、"2"(字符串索引)
– 如果原型上挂了方法,比如 Array.prototype.custom = () =>{},它也会吐出来。
for...of 的合法目标必须实现 Symbol.iterator 接口。数组、字符串、Set、Map、NodeList 都行;普通对象默认不行:
– [1, 2, 3] → 输出 1、2、3
– "hi" → 输出 "h"、"i"
– {a: 1} → 直接报错:TypeError: is not iterable
看遍历内容:一个读标签,一个拿实物
for...in 每次循环拿到的是属性名(key),哪怕你写 for (let i in arr),i 也是字符串 "0",不是数字 0,更不是值 arr[0]。
for...of 每次循环拿到的就是实实在在的值(value):for (let item of arr),item 就是 arr[0]、arr[1]……
想同时拿到键和值?别硬套 for...in 去“模拟”:
– 数组用 arr.entries() + for...of
– 对象用 Object.entries(obj) + for...of
– Map 原生支持 for (let [key, value] of map)
看行为边界:一个管不住嘴,一个守好自己
for...in 会顺着原型链往下翻,看到什么可枚举属性就吐什么。哪怕你只定义了三个属性,它也可能输出十几个——只要原型上有人加了可枚举方法或属性。
for...of 完全不碰原型链,它只调用对象自身的 [Symbol.iterator](),返回什么,它就拿什么。干净、可控、可预测。
常见踩坑场景:
– 用 for...in 遍历数组渲染列表,结果页面多出一堆 undefined(因为遍历到了原型方法)
– 给对象扩展了 obj.toJSON = () => {...},for...in 一跑,JSON 方法也被当数据塞进模板里
– 想在 for...of 里 break 退出,却误写成 return,结果函数提前结束——这不是语法错,是逻辑错,根源还是没分清它俩的语义定位
看底层机制:一个是枚举,一个是迭代
for...in 底层走的是“属性枚举协议”,靠 [[Enumerable]] 标志位判断是否该出现。
for...of 底层走的是“迭代器协议”,必须有 Symbol.iterator 方法,且该方法要返回一个带 next() 的迭代器对象。
这意味着:
– 你可以让普通对象支持 for...of,只要手动加 obj[Symbol.iterator] = function*(){ yield this.a; yield this.b; }
– 但你不能让字符串支持 for...in,因为它不是对象(虽有包装对象,但 for...in 不会自动装箱)
– for...in 不保证顺序(ES2015+ 对数字键做了排序,但非数字键仍不确定),for...of 严格按迭代顺序执行

















