for...in
试一试
语法
for (variable in object)
statement
参数
描述
循环将迭代对象本身的所有可枚举属性以及对象从其原型链继承的属性(更靠近原型的属性优先于其原型链中离对象更远的属性)。
与其他循环语句一样,您可以在 statement
中使用 控制流语句
for...in
循环仅迭代可枚举的非符号属性。从内置构造函数(如 Array
和 Object
)创建的对象从 Array.prototype
和 Object.prototype
继承了不可枚举的属性,例如 Array
的 indexOf()
方法或 Object
的 toString()
方法,这些方法不会在 for...in
循环中访问。
根据现代 ECMAScript 规范,遍历顺序是明确定义的,并且在不同的实现中保持一致。在原型链的每个组件中,所有非负整数键(可以作为数组索引的键)将首先按值升序遍历,然后其他字符串键按属性创建的升序时间顺序遍历。
for...in
的 variable
部分接受任何可以在 =
运算符之前出现的任何内容。您可以使用 const
声明变量,只要它在循环体中没有被重新赋值即可(它可以在迭代之间更改,因为它们是两个不同的变量)。否则,可以使用 let
。您可以使用 解构 赋值多个局部变量,或者使用属性访问器(如 for (x.y in iterable)
)将值分配给对象属性。
删除、添加或修改的属性
for...in
按以下方式访问属性键
- 它首先获取当前对象的所有自身字符串键,方式与
Object.getOwnPropertyNames()
非常相似。 - 对于每个键,如果之前从未访问过具有相同值的字符串,则 检索属性描述符,并且仅当属性是可枚举的时才访问该属性。但是,即使属性不可枚举,此属性字符串也会被标记为已访问。
- 然后,当前对象将被替换为其原型,并重复此过程。
这意味着
- 在迭代期间添加到当前访问对象的任何属性都不会被访问,因为当前对象的所有自身属性之前已经保存。
- 如果原型链中的多个对象具有相同名称的属性,则仅考虑第一个对象,并且仅当它可枚举时才访问它。如果它不可枚举,则原型链中更高层的其他具有相同名称的属性将不会被访问,即使它们是可枚举的。
一般来说,最好不要在迭代期间添加、修改或删除对象的属性,除了当前正在访问的属性之外。规范明确允许实现不在以下情况之一中遵循上述算法
- 对象的原型链在迭代期间被修改。
- 在迭代期间从对象或其原型链中删除属性。
- 在迭代期间将属性添加到对象的原型链中。
- 在迭代期间更改属性的可枚举性。
在这些情况下,实现的行为可能与您预期不同,甚至在不同的实现之间也不一致。
数组迭代和 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 风格指南和 linter 建议不要使用 for...in
,因为它会迭代整个原型链,而这很少是人们想要的,并且可能会与更广泛使用的 for...of
循环混淆。for...in
最实用的是用于调试目的,因为它是一种检查对象属性的简单方法(通过输出到控制台或其他方式)。在将对象用作临时键值对的情况下,for...in
允许您检查这些键中是否包含特定值。
示例
使用 for...in
下面的 for...in
循环遍历对象的所有可枚举非符号属性,并记录属性名称及其值的字符串。
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()
:不会显示继承的属性。
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
在某些极端情况下的行为。
在迭代期间添加到当前对象的属性永远不会被访问
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
阴影属性只被访问一次
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.
此外,请考虑以下情况,这些情况的行为未定义,并且实现往往偏离指定的算法
在迭代期间更改原型
const obj = { a: 1, b: 2 };
for (const prop in obj) {
console.log(`obj.${prop} = ${obj[prop]}`);
Object.setPrototypeOf(obj, { c: 3 });
}
在迭代期间删除属性
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;
}
在迭代期间添加到原型的可枚举属性
const proto = {};
const obj = { __proto__: proto, a: 1, b: 2 };
for (const prop in obj) {
console.log(`obj.${prop} = ${obj[prop]}`);
proto.c = 3;
}
在迭代期间更改属性的可枚举性
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 语言规范 # sec-for-in-and-for-of-statements |
浏览器兼容性
BCD 表格只在浏览器中加载