跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScriptNode.js大前端

JavaScript 中 for...in 与 for...of 的区别与用法

围绕 JavaScript 的 for...in 与 for...of 展开对比:for...in 迭代对象的可枚举属性名,会遍历自身及原型链上的非 Symbol 键;for...of 迭代可迭代对象的值,适用于数组、字符串、Map、Set 等。文中结合数组属性、Symbol 键、Object.defineProperty 和迭代器中断示例,说明两者的适用场景与核心区别。

lyfadvance发布于 2025/1/18更新于 2026/9/838 浏览
JavaScript 中 for...in 与 for...of 的区别与用法

for...in

for...in 用来遍历对象的可枚举属性。很多人第一次接触它时,会把它理解成'专门遍历数组下标'的语法,这种说法不算错,但只说了一半。它更准确的职责,是按属性名去遍历一个对象,而且会把原型链上可枚举的属性也一起带出来。

先看一个最常见的场景:

const arr = [1, 2, 3, 4, 5];
const obj = {
  mark: 'mark-v',
  jack: 'jack-v',
  amy: 'amy-v',
};

for (const i in arr) {
  // 输出 0 1 2 3 4
  console.log(i);
}

for (const i in obj) {
  // 输出 mark jack amy
  console.log(i);
}

数组里输出的是下标,对象里输出的是键名。这个结果并不意外,但它也容易让人误以为 for...in 的能力就这么点。实际上,真正值得注意的,是它背后处理属性的规则。

继续往下看:

const arr = [1, 2, 3, 4, 5];
arr.value = 'array'; // 给数组对象额外加一个属性

const s1 = Symbol('symbol1');
const s2 = Symbol('symbol2');

const obj = {
  mark: 'mark-v',
  jack: 'jack-v',
  amy: 'amy-v',
  [s1]: 'symbol1-v',
  [s2]: 'symbol2-v',
};

// 给原型链上挂一个可枚举属性
Object.getPrototypeOf(obj).myProto = 'proto';

for (const i in arr) {
  console.log(i);
  // 输出 0 1 2 3 4 value myProto
}

for (const i in obj) {
  console.log(i);
  // 输出 mark jack amy myProto
}

这里有三个点很关键:

  • for...in 只遍历可枚举属性。
  • Symbol 类型的键会被跳过,哪怕它本身也是可枚举的。
  • for...in 会继续沿着原型链找可枚举属性。

所以 arr 能遍历到 value 和 myProto,obj 也能遍历到 myProto。原因不是数组和对象'共享了某种特殊能力',而是它们的原型链最终都会碰到 Object.prototype。

Object.getPrototypeOf(obj) === Object.getPrototypeOf(Object.getPrototypeOf(arr)); // true

这也说明了一个容易被忽略的事实:for...in 不是只看'这个对象自己身上有什么',它还会顺着原型链继续往上扫。实际开发里,如果你只想遍历对象自身属性,通常要配合 hasOwnProperty 做过滤。

什么是可枚举属性

可枚举属性指的是属性描述符里 enumerable 为 true 的属性。

  • 通过对象字面量定义的属性,默认就是 true
  • 通过 Object.defineProperty 定义的属性,默认是 false
  • Symbol 键也可以是可枚举的,只是 for...in 不会遍历它们

看一个更完整的例子:

const s1 = Symbol('symbol1');
const s2 = Symbol('symbol2');

const obj = {
  mark: 'mark-v',
};

obj.jack = 'jack-v';
obj['amy'] = 'amy-v';

Object.defineProperty(obj, 'kong', {
  value: 'kong-v',
  enumerable: false,
});

Object.defineProperty(obj, 'john', {
  value: 'john-v',
  enumerable: true,
});

Object.defineProperty(obj, s1, {
  value: 's1-v',
  enumerable: false,
});

Object.defineProperty(obj, s2, {
  value: 's2-v',
  enumerable: true,
});

for (const i in obj) {
  console.log(i);
  // 输出 mark jack amy john
}

这里能看到,kong 没有被遍历出来,因为它是不可枚举属性;john 可以被遍历到,因为我们显式把它设成了可枚举。至于两个 Symbol 键,即使 s2 设置成了 enumerable: true,for...in 还是会跳过它。

数组下标也是属性

数组看起来是'有顺序的一组值',但从对象模型上看,它的下标本质上也是属性。你可以把数组当成一种特殊对象来理解。

const arr = [1, 2, 3, 4, 5];

Object.defineProperty(arr, 0, {
  value: 100,
  enumerable: false,
  writable: false,
});

arr[0] = 1;

console.log(arr); // [ 100, 2, 3, 4, 5 ]

for (const i in arr) {
  console.log(i);
  // 输出 1 2 3 4
}

这段代码很适合帮助理解'数组下标也是属性'这件事:

  • 把 0 号下标设为不可枚举后,for...in 遍历不到它
  • 把 writable 设为 false 后,直接赋值也改不动它
  • 但数组本身打印出来时,依然会显示完整内容,因为控制台展示的是对象的当前状态,不等于 for...in 的遍历规则

for...of

for...of 的思路就完全不一样了。它不是按属性名来遍历对象,而是遍历可迭代对象的值。只要一个对象实现了迭代器接口,就可以被 for...of 消费。

常见的可迭代对象包括:数组、字符串、Map、Set 等。

// 迭代数组
const array = [10, 20, 30];
for (const value of array) {
  console.log(value); // 10 20 30
}

// 迭代字符串
const string = 'kong';
for (const s of string) {
  console.log(s); // k o n g
}

// 迭代 Map
const map = new Map([
  ['a', 1],
  ['b', 2],
  ['c', 3],
]);
for (const item of map) {
  console.log(item); // [ 'a', 1 ] [ 'b', 2 ] [ 'c', 3 ]
}

// 迭代 Set
const set = new Set([1, 1, 2, 2, 3, 3]);
for (const item of set) {
  console.log(item); // 1 2 3
}

如果你注意观察,会发现 for...of 关心的是'值',而不是'键'。这也是它和 for...in 最本质的区别。

迭代器可以被中断

for...of 循环可以被 break、throw 或 return 终止。迭代器在这些情况下会被关闭,这在处理流式数据或自定义迭代器时尤其重要。

const array = [10, 20, 30];

for (const value of array) {
  console.log(value);
  return;
}

// 下面的代码不会执行
const string = 'kong';
for (const s of string) {
  console.log(s);
}
const array = [10, 20, 30];

for (const value of array) {
  console.log(value);
  throw new Error();
}

// 不会执行到这里
const string = 'kong';
for (const s of string) {
  console.log(s);
}

这一点和 for...in 类似:循环体里的 break、throw、return 都能让循环提前结束。不同的是,for...of 背后牵涉的是迭代器协议,终止时还会做相应的关闭处理。

总结

  • for...in 用来遍历对象的可枚举属性名,会遍历对象自身以及原型链上的可枚举属性,但会跳过 Symbol 键
  • for...of 用来遍历可迭代对象的值,常见对象有数组、字符串、Map、Set
  • 如果你想遍历数组元素,优先考虑 for...of
  • 如果你确实需要拿到对象的键名,并且还要处理原型链上的枚举属性,for...in 才是对应的工具

理解这两者,关键不在于记住表面现象,而在于分清:一个是在遍历属性名,一个是在遍历值。只要把这个边界拎清楚,面试里常见的追问也就不难接了。

目录

  1. for...in
  2. 什么是可枚举属性
  3. 数组下标也是属性
  4. for...of
  5. 迭代器可以被中断
  6. 总结

更多推荐文章

查看全部
  • Seata XA 模式:强一致性分布式事务的配置与权衡
  • 树中所有节点到其他节点的距离之和
  • Lit-LLaMA 大语言模型部署与微调指南
  • C++11 核心特性:列表初始化与右值引用移动语义详解
  • Bodymovin 开源动画转换工具跨平台集成方案
  • C 语言数据结构:单链表详解与实现
  • Mac mini 对比 NUC 与树莓派:OpenClaw 硬件平台选型分析
  • Java 零基础完整入门教程
  • AI 绘画商业变现模式与引流实操指南
  • VR 大空间项目内容规划与设计:2023-2026 市场实战复盘
  • 基于.NET 6 集成 GoView 低代码可视化大屏实战指南
  • 大模型技术教程:从基础入门到实战应用
  • Ubuntu 20.04 安装 Ollama 及 Open WebUI 本地部署 LLM 教程
  • Kiro 与 Cursor 深度对比:AI 编程助手体验
  • Memcached 核心命令与监控统计详解
  • 使用回调接口将 AI 小助手接入企业微信群聊机器人
  • Rust 与 WebAssembly 实战:在浏览器与 Node.js 中运行高性能代码
  • 基于 Java 的电子发票 OFD 文件数字签名解析与验真
  • AI视频角色一致性怎么破?2026最新稳定方案大公开
  • C++ 从零实现 K-Means 聚类算法详解

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online