深度解析for...in和for...of究竟有啥区别(麻麻再也不担心,我学不明白啦)
Category(分类): JavaScript Status: 已整理
原稿只保留了一个 CSDN 原文链接。本文按原文(CSDN tietouachai,2023 年 1 月)的完整结构与示例重新收录,保留其叙事风格,并修正其中的变量名错乱、break/continue 注释错误、
undefine拼写等问题,补充 for...in 的属性顺序、对象为何不可迭代、for await...of 等现代内容。文末附勘误清单。
前言
该篇文章会给你普及并且深入 for…in 和 for…of 的遍历的相关知识,还会给你介绍对象属性描述符和迭代器这两个知识点(简单概念,会在其他文章详讲),当你理解了这两个知识点后,你就会真正的明白 for…in 和 for…of 的使用场景,篇幅较长,希望大家能够坚持看下去,有所收获🎉🎉🎉
这里先提前告诉你主要内容:for…in 是用来枚举对象属性,for…of 用来遍历迭代器(有序结构数据)
一、对象属性描述符 - for…in
1. 获取对象属性描述符的 API
首先我们定义一个对象,并声明一个属性:
const obj = { ikun: 666 }
获取属性描述符状态 API:Object.getOwnPropertyDescriptor()
console.log(Object.getOwnPropertyDescriptor(obj, 'ikun'))
// { value: 666, writable: true, enumerable: true, configurable: true }
(原文此处为截图,图床已失效,已用当前 Node 实测输出替代。)这里我们可以看到几个属性:configurable、enumerable、value、writable,这里面只有 enumerable(可枚举)会对 for...in 枚举有影响。
其他属性该篇文章不做过深讲解,会写篇文章进行详讲
2. 设置对象属性描述符的 API
设置属性描述符的 API 是 Object.defineProperty()(原文此处误写为「获取」,已更正):
Object.defineProperty(obj, 'rap', {
value: 'music',
// ...
})

3. enumerable
接下来通过 Object.defineProperty() 来给对象添加属性并配置:
const obj = { ikun: 666 }
Object.defineProperty(obj, 'music', {
value: 200,
enumerable: false
})
Object.defineProperty(obj, 'basketball', {
value: '基尼太美',
enumerable: true
})
for (let key in obj) {
console.log(key) // ikun basketball
}
此时只能遍历出来 ikun、basketball 两个属性,但可以通过 in 来判断某个属性是否存在:
console.log('ikun' in obj) // true
console.log('music' in obj) // true
console.log('basketball' in obj) // true
设置了 enumerable: false 后,无法被 for...in 遍历,但可以通过 in 来判断是否存在该属性。
4. 原型链属性和方法的 enumerable
学过 JavaScript 高阶语法的同学都应该知道,当我们在获取对象属性的时候,它会先在自身上寻找有没有这个属性,自身没有这个属性,则会在原型链上面进行寻找。
当我们在 for...in 枚举属性时,它也会把原型链上面的属性和方法一起枚举出来。
但你们知道为啥在使用 for...in 的时候并没有遍历出来原型链上的属性和方法吗?无奖竞猜🎈
3
2
1
答案:内置原型上的属性和方法都被设置了 enumerable: false
5. 实验:for…in 会不会遍历原型链上面的属性
是吗?我不信?好小子,那我们可以来做一个实验,我们来获取所有原型链最终的归宿 Object.prototype 身上的 toString 方法身上的对象属性描述符:
console.log(Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable)
// false

这里可以看到 toString 方法的 enumerable 被设置成了 false。
接下来我们介绍一个新的 API Object.create(),用于将指定数据的原型链指向其他数据:
const obj_prototype = {
ikun: 666,
music: 'music',
basketball: '篮球'
}
const obj = Object.create(obj_prototype)
console.log(obj.__proto__) // { ikun: 666, music: 'music', basketball: '篮球' }
console.log(obj) // {}
obj 为空对象,而它的原型链上有数据。可以看到已经指向成功了,这时我们通过 for...in 进行枚举 obj,看看结果如何:

经过实测(Node 24):for (const k in obj) 依次输出 ikun、music、basketball——原型链上的可枚举属性确实会被枚举出来。经过证实,这次信了吧?你要是还不信,就不让你吃我家鸽鸽下的蛋了🙄
二、迭代器 - for…of
1. 什么是迭代器
所有的有序结构(如数组、NodeList)内部都有内置的迭代器。
该篇文章不会详讲迭代器,会在另一篇文章进行详讲,这里先说说大致内容
2. 有序结构
- 字符串
- 数组
- NodeList 等 DOM 集合
- Map
- Set
- arguments
- generators(生成器对象,现代补充)
注意:普通 object 对象不属于可迭代对象——它没有定义
Symbol.iterator方法。至于「对象内部顺序不能保证有序」这个理由其实不成立:ES2015 之后对象自身的字符串键是按插入顺序(整数键升序在前)遍历的。对象不可迭代的真正原因只是:规范没有给Object.prototype定义Symbol.iterator。(原文此条解释已按规范更正。)
3. 每个迭代器都会有一个属性 Symbol.iterator
实验证明有序数据存在 Symbol.iterator 属性:
let arr = [100, 200, 300]
console.log(arr[Symbol.iterator]) // ƒ values() { [native code] }
console.log(arr[Symbol.iterator]())
// Array Iterator {} —— 原型链身上会有一个叫 next 的方法

4. 为什么会存在 next 方法
next 方法就是用来帮助遍历的一个方法,每次调用这个 next 方法,就会返回一个对象。让我们调用四次:
let arr = [100, 200, 300]
const it = arr[Symbol.iterator]() // 注意:要复用同一个迭代器
console.log(it.next()) // { value: 100, done: false }
console.log(it.next()) // { value: 200, done: false }
console.log(it.next()) // { value: 300, done: false }
console.log(it.next()) // { value: undefined, done: true }(原文误写为 undefine)
arr 数组只存在三个元素,当第四次调用 next() 时,返回对象的 done 则为 true,表示终止没有内容了。
现在我们使用 for...of 进行遍历 arr 迭代器(数组):
let arr = [100, 200, 300]
for (let n of arr) {
console.log(n) // 100 200 300
}
So?你现在能猜到 for...of 的运行方式了吗?
该篇文章不会详讲 next,会在另一篇迭代器文章中进行详讲,这里先说说大致内容
5. 结论
当我们通过 for...of 进行遍历迭代器(有序结构)时,它会帮我们调用内部迭代器的 next 方法,每次调用通过返回对象的 done 属性判断是否遍历完了全部数据。
三、for…in
1. 介绍
for...in 语句以任意顺序枚举一个对象的除 Symbol 以外的可枚举属性,包括从原型链上继承的可枚举属性,一般用于枚举对象,但可以枚举数组(可以用但没必要)。
回忆一下对象属性描述符上的 enumerable 属性
现代注解(遍历顺序):「任意顺序」是规范留给实现的自由度,但 ES2015 之后所有主流引擎都遵循同一套顺序规则(由
[[OwnPropertyKeys]]决定):先按数值升序遍历整数索引键,再按插入顺序遍历其余字符串键(Symbol 键不参与 for...in)。实测{ b: 1, 2: 2, a: 3, 1: 4 }的 for...in 输出顺序是1, 2, b, a。所以现代代码可以依赖这个顺序,但跨引擎的极端兼容场景仍建议不要依赖。
2. 使用示例
const obj = {
ikun: 'ikun',
rap: 'rap'
}
for (let key in obj) {
console.log(key) // ikun, rap
}
3. 枚举数组(可以但不适合)
使用 for...in 去遍历数组,得到的 key 就是数组的索引 index:
const arr = [
{ ikun: 'ikun' },
{ music: 'music' }
]
for (let key in arr) {
console.log(key) // 0 1
console.log(arr[key]) // { ikun: 'ikun' }, { music: 'music' }
}
一般都使用 for...of 来进行遍历。for...in 遍历数组的坑:它枚举的是「索引字符串 + 原型链上可枚举属性」,任何挂到 Array.prototype 上的可枚举扩展都会被枚举出来,而且顺序对稀疏数组/非索引键的处理并不按数组语义来。
4. for…in 如何中断循环
在 for...in 中可以使用 break 或者 continue 去中断/跳过循环,不可以直接用 return 去中断循环:
const obj = {
ikun: 'ikun',
music: 'music',
rap: 'rap'
}
// continue:跳过当前项,继续后面的遍历
for (let key in obj) {
if (key === 'ikun') continue
console.log(key) // music, rap
}
// break:彻底中断整个循环
let collected = []
for (let key in obj) {
if (key === 'music') break
collected.push(key)
}
console.log(collected) // ['ikun'](原示例把 break 的注释写成了 continue 的效果,已修正)
但如果你是杠精的话说:for...in 可以使用 return 中断循环,那就只能将 for…in 放到函数中了(return 属于函数,不属于循环),可以但没必要。
5. 枚举带有 Symbol 的对象
const obj = {
ikun: 'ikun',
rap: 'rap',
[Symbol('music')]: '我是时长两年半的个人练习生'
}
for (let key in obj) {
console.log('obj.' + key + ' = ' + obj[key])
}
// 只输出 obj.ikun 和 obj.rap
这里可以看到,不能遍历出 Symbol 属性:

6. 如何枚举对象身上的 Symbol 属性
(原文此节截图所在的第三方图床已失效,以下为当前 Node 实测输出。)
const obj = { ikun: 'ikun', rap: 'rap', [Symbol('music')]: '练习生' }
console.log(Reflect.ownKeys(obj))
// ['ikun', 'rap', Symbol(music)] —— 所有键,包含 Symbol
console.log(Object.getOwnPropertySymbols(obj))
// [Symbol(music)] —— 仅 Symbol 键
console.log(Object.getOwnPropertyNames(obj))
// ['ikun', 'rap'] —— 字符串键,不含 Symbol
console.log(Object.keys(obj))
// ['ikun', 'rap'] —— 仅自身可枚举的字符串键
一句话记忆:Reflect.ownKeys = getOwnPropertyNames + getOwnPropertySymbols(且都包含不可枚举键);Object.keys 只包含自身可枚举的字符串键。
四、for…of
1. 介绍
for...of 语句以顺序遍历有序结构的数据,详细情况见「二、迭代器 - for…of」。
回忆一下迭代器上的 next 方法
2. 使用示例
var arr = ['ikun', 'singing', 'jump', 'rap', 'music', 'basketball']
for (let item of arr) {
console.log(item) // 'ikun', 'singing', 'jump', 'rap', 'music', 'basketball'
}
遍历数组里的每一项,并得到每一个元素。
3.(现代补充)for…of 的边界
// 普通对象没有 Symbol.iterator,直接 TypeError
try {
for (const v of { a: 1 }) console.log(v)
} catch (e) {
console.log(e.constructor.name) // TypeError
}
// 遍历对象请配合 entries/keys/values
const obj = { a: 1, b: 2 }
for (const [k, v] of Object.entries(obj)) console.log(k, v) // a 1, b 2
// break / continue 同样可用;解构、展开、Array.from 走的也是同一套迭代器协议
const [x, ...rest] = [1, 2, 3]
const copy = [...arr]
// 异步可迭代对象用 for await...of(ES2018)
async function main() {
for await (const chunk of fetchSomeStream()) console.log(chunk)
}
另外,字符串迭代按 Unicode 码位逐个产出(for...of 'a😀b' 产出 'a'、'😀'、'b',而 for...in 按下标),这也是处理含 emoji 文本时 for...of 更安全的原因。
总结
for...in 专门用于枚举对象数据:
- 内部原理通过
enumerable对象属性描述符来判断是否可枚举的属性 - 会连原型链上的可枚举属性一起枚举,需要自身属性时配合
Object.prototype.hasOwnProperty或改用Object.keys
for...of 专门用于遍历有序结构数据(数组、NodeList DOM 集合、Map、Set、字符串等):
- 内部原理通过内置迭代器的
next方法返回的对象来判断是否可以遍历 - 要求对象实现
Symbol.iterator,普通对象需要先转成Object.entries()等形式
该篇文章知识点从慕课网双越老师的视频《快速掌握前端必会的 7 种设计模式》处习得,如有侵权,联系删除
原文勘误与现代化说明
原稿仅为一个 CSDN 链接占位,本文按原文完整重录,并修正原文以下问题:
- break/continue 示例错误:原文示例定义的变量是
arr,循环里却写for (let key in obj)(未定义),且 break 的输出注释写成music, rap(那是 continue 的效果)。已改为变量一致的示例,并分别演示 continue(输出 music、rap)与 break(只收集到 ikun)。 {value: undefine, done: true}拼写更正为undefined;同一示例中每次都调用arr[Symbol.iterator]().next()会得到四个全新迭代器的第一个值,已改为复用同一个迭代器,与输出注释一致。- 「对象不属于迭代器,因为内部顺序不能保证有序」:更正为「普通对象没有定义 Symbol.iterator;顺序问题与可迭代性无关,ES2015 后对象键顺序有明确规定」。
- **「获取属性描述符状态 API:Object.defineProperty()」**笔误更正为「设置」。
- 补充现代内容:for...in 的属性顺序规则(整数键升序 + 字符串键插入序,实测验证)、对象不可迭代时的 TypeError、
Object.entries/keys/values配合 for...of、for await...of、字符串按码位迭代、Reflect.ownKeys/getOwnPropertyNames/getOwnPropertySymbols/Object.keys的区分。 - 原文两张截图(getOwnPropertyDescriptor 输出、Reflect.ownKeys 输出)所在的第三方图床(oss.zhishiyu.online)已失效,用当前 Node 的实测输出文本替代;其余 5 张截图已本地化为
images/114-image-01~05.webp。
图片来源与授权说明
正文图片已本地化到 images/ 目录,均来自原文(CSDN tietouachai)的图床资源。图片的转载授权无法仅凭下载确认,公开发布前应向原作者确认许可:
images/114-image-01.webp:Object.defineProperty MDN 文档截图。来源:原文插图。images/114-image-02.webp:Object.prototype.toString属性描述符控制台输出。来源:原文截图。images/114-image-03.webp:for...in 遍历原型链属性的输出。来源:原文截图。images/114-image-04.webp:数组迭代器与 next 方法控制台输出。来源:原文截图。images/114-image-05.webp:for...in 无法枚举 Symbol 键的输出。来源:原文截图。
原文归属
作者:tietouachai。历史来源:CSDN《深度解析for...in和for...of究竟有啥区别(麻麻再也不担心,我学不明白啦)》。原文链接仅用于保留历史出处,当前语义以本文列出的规范和官方文档为准。