Get to know MDN better
在每次迭代时接收一个字符串属性名。它可以通过使用 const、let 或 var 进行声明,也可以是一个赋值目标(例如,先前声明的变量、对象属性或解构模式)。使用 var 声明的变量不会局限于循环内部,即它们与 for...in 循环所在的作用域相同。
object被迭代非符号可枚举属性的对象。
statement每次迭代后执行的语句。可以引用 variable。可以使用块语句执行多个语句。
该循环将迭代对象本身的所有可枚举属性,以及对象从其原型链继承的属性(原型链中较近的原型的属性优先于较远的原型的属性)。
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 循环中的 var 声明的循环变量具有初始值。在严格模式下,这会抛出语法错误,而在非严格模式下则会被忽略。
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 循环迭代对象所有可枚举的非符号属性,并输出属性名和对应值的字符串。
下面的函数展示了 Object.hasOwn() 的用法:继承的属性不会显示。
警告:你不应该自己编写类似的代码。这里仅仅展示了 for...in 在某些极端情况下的行为。
在迭代期间添加到当前对象的属性永远不会被访问:
被遮蔽(shadowed)的属性只会被访问一次:
此外,请考虑以下情况,即行为是未指定的,并且实现往往偏离规定的算法:
在迭代过程中更改原型链:
在迭代过程中删除属性:
在迭代过程中向原型链添加可枚举属性:
在迭代过程中修改属性的可枚举性:
| ECMAScript® 2027 Language Specification # sec-for-in-and-for-of-statements |
启用 JavaScript 以查看此浏览器兼容性表。