for...in

for...in 语句迭代对象的所有 可枚举字符串属性(忽略由 symbols 键控的属性),包括继承的可枚举属性。

¥The for...in statement iterates over all enumerable string properties of an object (ignoring properties keyed by symbols), including inherited enumerable properties.

Try it

语法

¥Syntax

js
for (variable in object)
  statement

参数

¥Parameters

variable

在每次迭代时接收字符串属性名称。可以是带有 constletvar 的声明,也可以是 assignment 目标(例如先前声明的变量、对象属性或 解构赋值模式)。用 var 声明的变量不是循环的本地变量,即它们与 for...in 循环位于同一范围内。

object

对其非符号可枚举属性进行迭代的对象。

statement

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

描述

¥Description

该循环将迭代对象本身的所有可枚举属性以及对象从其原型链继承的属性(较近原型的属性优先于原型链中远离对象的原型的属性)。

¥The loop will iterate over all enumerable properties of the object itself and those the object inherits from its prototype chain (properties of nearer prototypes take precedence over those of prototypes further away from the object in its prototype chain).

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

¥A for...in loop only iterates over enumerable, non-symbol properties. Objects created from built–in constructors like Array and Object have inherited non–enumerable properties from Array.prototype and Object.prototype, such as Array's indexOf() method or Object's toString() method, which will not be visited in the for...in loop.

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

¥The traversal order, as of modern ECMAScript specification, is well-defined and consistent across implementations. Within each component of the prototype chain, all non-negative integer keys (those that can be array indices) will be traversed first in ascending order by value, then other string keys in ascending chronological order of property creation.

for...invariable 部分接受 = 运算符之前的任何内容。你可以使用 const 来声明变量,只要它不在循环体内重新分配即可(它可以在迭代之间更改,因为它们是两个单独的变量)。否则,你可以使用 let。你可以使用 destructuring 分配多个局部变量,或使用像 for (x.y in iterable) 这样的属性访问器将值分配给对象属性。

¥The variable part of for...in accepts anything that can come before the = operator. You can use const to declare the variable as long as it's not reassigned within the loop body (it can change between iterations, because those are two separate variables). Otherwise, you can use let. You can use destructuring to assign multiple local variables, or use a property accessor like for (x.y in iterable) to assign the value to an object property.

遗留语法 允许循环变量的 var 声明具有初始值设定项。这在严格模式下会抛出 语法错误,在非严格模式下会被忽略。

¥A legacy syntax allows var declarations of the loop variable to have an initializer. This throws a syntax error in strict mode and is ignored in non–strict mode.

删除、添加或修改的属性

¥Deleted, added, or modified properties

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

¥for...in visits property keys in the following fashion:

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

这意味着:

¥This means:

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

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

¥In general, it is best not to add, modify, or remove properties from the object during iteration, other than the property currently being visited. The spec explicitly allows the implementation to not follow the algorithm above in one of the following cases:

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

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

¥In these cases, implementations may behave differently from what you may expect, or even from each other.

数组迭代和 for...in

¥Array iteration and for...in

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

¥Array indexes are just enumerable properties with integer names and are otherwise identical to general object properties. The for...in loop will traverse all integer keys before traversing other keys, and in strictly increasing order, making the behavior of for...in close to normal array iteration. However, the for...in loop will return all enumerable properties, including those with non–integer names and those that are inherited. Unlike for...of, for...in uses property enumeration instead of the array's iterator. In sparse arrays, for...of will visit the empty slots, but for...in will not.

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

¥It is better to use a for loop with a numeric index, Array.prototype.forEach(), or the for...of loop, because they will return the index as a number instead of a string, and also avoid non-index properties.

仅迭代自己的属性

¥Iterating over own properties only

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

¥If you only want to consider properties attached to the object itself, and not its prototypes, you can use one of the following techniques:

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

¥Object.keys will return a list of enumerable own string properties, while Object.getOwnPropertyNames will also contain non-enumerable ones.

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

¥Many JavaScript style guides and linters recommend against the use of for...in, because it iterates over the entire prototype chain which is rarely what one wants, and may be a confusion with the more widely-used for...of loop. for...in is most practically used for debugging purposes, being an easy way to check the properties of an object (by outputting to the console or otherwise). In situations where objects are used as ad hoc key-value pairs, for...in allows you check if any of those keys hold a particular value.

示例

¥Examples

用于...中

¥Using for...in

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

¥The for...in loop below iterates over all of the object's enumerable, non-symbol properties and logs a string of the property names and their values.

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"

迭代自己的属性

¥Iterating own properties

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

¥The following function illustrates the use of Object.hasOwn(): the inherited properties are not displayed.

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"

并发修改

¥Concurrent modification

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

¥Warning: You should not write code like this yourself. It is only included here to illustrate the behavior of for...in in some corner cases.

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

¥Properties added to the current object during iteration are never visited:

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

阴影属性仅被访问一次:

¥Shadowed properties are only visited once:

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.

此外,请考虑以下情况,其中行为未指定,并且实现往往会偏离指定的算法:

¥In addition, consider the following cases, where the behavior is unspecified, and implementations tend to diverge from the specified algorithm:

在迭代期间更改原型:

¥Changing the prototype during iteration:

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

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

在迭代期间删除属性:

¥Deleting a property during iteration:

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;
}

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

¥Enumerable properties added to the prototype during iteration:

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;
}

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

¥Changing the enumerability of a property during iteration:

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 });
}

规范

Specification
ECMAScript Language Specification
# sec-for-in-and-for-of-statements

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看