for...in

Baseline 已广泛支持

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2015 年 7 月⁩以来,各浏览器均已提供此特性。

for...in 语句遍历对象的所有可枚举字符串属性(忽略以符号为键的属性),包括继承的可枚举属性。

试一试

const object = { a: 1, b: 2, c: 3 };

for (const property in object) {
  console.log(`${property}: ${object[property]}`);
}

// Expected output:
// "a: 1"
// "b: 2"
// "c: 3"

语法

js
for (variable in object)
  statement

参数

variable

在每次迭代中接收一个字符串属性名称。可以是使用 constletvar 声明的变量,也可以是赋值目标(例如,之前声明的变量、对象属性或解构模式)。使用 var 声明的变量不是循环的局部变量,也就是说,它们与 for...in 循环处于相同的作用域中。

object

要遍历其非符号可枚举属性的对象。

statement

每次迭代时执行的语句。可以引用 variable。你可以使用块语句来执行多条语句。

描述

循环将遍历对象自身的所有可枚举属性,以及对象从其原型链继承的属性(距离对象原型链更近的原型属性优先于距离对象更远的原型属性)。

像其他循环语句一样,你可以在 statement 中使用控制流语句

  • break 会停止 statement 的执行,并跳转到循环之后的第一条语句。
  • continue 会停止 statement 的执行,并进入循环的下一次迭代。

for...in 循环只遍历可枚举的、非符号属性。由 ArrayObject 等内置构造函数创建的对象从 Array.prototypeObject.prototype 继承了不可枚举的属性,例如 ArrayindexOf() 方法或 ObjecttoString() 方法,这些属性在 for...in 循环中不会被访问。

根据现代 ECMAScript 规范,遍历顺序是明确定义的,并且在不同实现中保持一致。在原型链的每个组成部分中,所有非负整数键(可以作为数组索引的键)将首先按值升序遍历,然后其他字符串键按属性创建的升序时间顺序遍历。

for...invariable 部分接受任何可以放在 = 运算符前面的内容。你可以使用 const 声明变量,只要它在循环体内部不被重新赋值即可(它可以在迭代之间改变,因为那是两个独立的变量)。否则,你可以使用 let。你可以使用解构来赋值多个局部变量,或者使用属性访问器,例如 for (x.y in iterable) 将值赋值给对象属性。但是,不允许使用 usingawait using,因为变量始终是字符串或符号,而这些声明需要一次性对象。

一种遗留语法允许循环变量的 var 声明带有一个初始化器。这在严格模式下会抛出语法错误,在非严格模式下会被忽略。

已删除、添加或修改的属性

for...in 以下列方式访问属性键

  1. 它首先获取当前对象的所有自有字符串键,其方式与 Object.getOwnPropertyNames() 非常相似。
  2. 对于每个键,如果从未访问过具有相同值的字符串,则检索属性描述符,并且仅当属性可枚举时才访问该属性。但是,即使该属性字符串不可枚举,它也会被标记为已访问。
  3. 然后,当前对象被替换为其原型,并重复该过程。

这意味着

  • 在迭代期间添加到当前对象的任何属性都不会被访问,因为当前对象的所有自有属性都已事先保存。
  • 如果原型链中的多个对象具有相同名称的属性,则只会考虑第一个,并且只有当它可枚举时才会被访问。如果它不可枚举,则原型链中具有相同名称的其他属性都不会被访问,即使它们可枚举。

通常,最好不要在迭代期间添加、修改或删除对象的属性,除非是当前正在访问的属性。规范明确允许实现在以下情况之一中不遵循上述算法:

  • 在迭代期间对象的原型链被修改。
  • 在迭代期间从对象或其原型链中删除属性。
  • 在迭代期间向对象的原型链添加属性。
  • 在迭代期间属性的可枚举性发生改变。

在这些情况下,实现的行为可能与你预期的不同,甚至彼此之间也可能不同。

数组迭代和 for...in

数组索引只是具有整数名称的可枚举属性,并且与一般的对象属性相同。for...in 循环将先遍历所有整数键,然后遍历其他键,并且严格按照递增顺序,这使得 for...in 的行为接近正常的数组迭代。但是,for...in 循环将返回所有可枚举属性,包括那些具有非整数名称和那些继承的属性。与 for...of 不同,for...in 使用属性枚举而不是数组的迭代器。在稀疏数组中,for...of 将访问空槽,但 for...in 不会。

最好使用带有数字索引的 for 循环、Array.prototype.forEach()for...of 循环,因为它们将以数字而不是字符串的形式返回索引,并且还可以避免非索引属性。

仅遍历自有属性

如果你只想考虑附加到对象本身的属性,而不是其原型的属性,你可以使用以下技术之一:

Object.keys 将返回可枚举的自有字符串属性列表,而 Object.getOwnPropertyNames 也会包含不可枚举的属性。

许多 JavaScript 样式指南和代码检查工具不建议使用 for...in,因为它会遍历整个原型链,这通常不是我们想要的,并且可能会与更广泛使用的 for...of 循环混淆。for...in 最实际的用途是用于调试目的,作为一种检查对象属性的简单方法(通过输出到控制台或其他方式)。在对象用作临时键值对的情况下,for...in 允许你检查这些键中是否包含特定值。

示例

使用 for...in

下面的 for...in 循环遍历对象的所有可枚举、非符号属性,并记录属性名称及其值的字符串。

js
const obj = { a: 1, b: 2, c: 3 };

for (const prop in obj) {
  console.log(`obj.${prop} = ${obj[prop]}`);
}

// Logs:
// "obj.a = 1"
// "obj.b = 2"
// "obj.c = 3"

遍历自有属性

以下函数演示了 Object.hasOwn() 的使用:不显示继承的属性。

js
const triangle = { a: 1, b: 2, c: 3 };

function ColoredTriangle() {
  this.color = "red";
}

ColoredTriangle.prototype = triangle;

const obj = new ColoredTriangle();

for (const prop in obj) {
  if (Object.hasOwn(obj, prop)) {
    console.log(`obj.${prop} = ${obj[prop]}`);
  }
}

// Logs:
// "obj.color = red"

并发修改

警告:你不应该自己编写这样的代码。这里包含它只是为了说明 for...in 在某些极端情况下的行为。

在迭代期间添加到当前对象的属性永远不会被访问

js
const obj = { a: 1, b: 2 };

for (const prop in obj) {
  console.log(`obj.${prop} = ${obj[prop]}`);
  obj.c = 3;
}

// Logs:
// obj.a = 1
// obj.b = 2

被遮蔽的属性只被访问一次

js
const proto = { a: 1 };
const obj = { __proto__: proto, a: 2 };

for (const prop in obj) {
  console.log(`obj.${prop} = ${obj[prop]}`);
}

// Logs:
// obj.a = 2

Object.defineProperty(obj, "a", { enumerable: false });

for (const prop in obj) {
  console.log(`obj.${prop} = ${obj[prop]}`);
}
// Logs nothing, because the first "a" property visited is non-enumerable.

此外,请考虑以下情况,其中行为未指定,并且实现往往与指定算法不同:

在迭代期间改变原型

js
const obj = { a: 1, b: 2 };

for (const prop in obj) {
  console.log(`obj.${prop} = ${obj[prop]}`);
  Object.setPrototypeOf(obj, { c: 3 });
}

在迭代期间删除属性

js
const obj = { a: 1, b: 2, c: 3 };

// Deleting a property before it is visited
for (const prop in obj) {
  console.log(`obj.${prop} = ${obj[prop]}`);
  delete obj.c;
}

const obj2 = { a: 1, b: 2, c: 3 };

// Deleting a property after it is visited
for (const prop in obj2) {
  console.log(`obj2.${prop} = ${obj2[prop]}`);
  delete obj2.a;
}

在迭代期间添加到原型的可枚举属性

js
const proto = {};
const obj = { __proto__: proto, a: 1, b: 2 };

for (const prop in obj) {
  console.log(`obj.${prop} = ${obj[prop]}`);
  proto.c = 3;
}

在迭代期间改变属性的可枚举性

js
const obj = { a: 1, b: 2, c: 3 };

for (const prop in obj) {
  console.log(`obj.${prop} = ${obj[prop]}`);
  Object.defineProperty(obj, "c", { enumerable: false });
}

规范

规范
ECMAScript® 2026 语言规范
# sec-for-in-and-for-of-statements

浏览器兼容性

另见