JavaScript循环:for...in和for的区别

16

我在Javascript中遇到了一种奇怪的行为。在以下代码中,对removeAttribute函数抛出了异常:

"对象不支持此属性或方法"
var buttons = controlDiv.getElementsByTagName("button");
for ( var button in buttons )
    button.removeAttribute('disabled');

当我使用以下代码更改时,问题消失了:

var buttons = controlDiv.getElementsByTagName("button");
for ( var i = 0; i < buttons.length; i++ )
    buttons[i].removeAttribute('disabled');

for...in 循环中 button 的值是什么?


尝试使用以下代码:for ( var button in buttons ) alert( button );,这样你就可以看到 for .. inbutton 设为了什么。 - Thai
4个回答

48

不要使用 for..in 遍历数组。

重要的是要理解,Javascript 数组的方括号语法 ([]) 用于访问索引实际上是从 Object 继承而来的......

obj.prop === obj['prop']  // true
for..in 结构不像其他语言(如 PHP、Python 等)中常见的更传统的 for..each/in。Javascript 的 for..in 适用于迭代对象的属性,产生每个属性的键。使用这个键结合 Object 的方括号语法,您可以轻松地访问所需的值。
var obj = {
    foo: "bar",
    fizz: "buzz",
    moo: "muck"
};

for ( var prop in obj ) {
    console.log(prop);      // foo / fizz / moo
    console.log(obj[prop]); // bar / buzz / muck
}

由于数组是一个具有按顺序排列的数字属性名(索引)的对象,因此for..in以类似的方式工作,产生与上面产生的属性名称相同的数字索引。

for..in结构的一个重要特点是它继续沿着原型链搜索可枚举属性。它也会迭代继承的可枚举属性。你需要使用hasOwnProperty()验证当前属性是否存在于本地对象上而不是其所附加的原型上...

for ( var prop in obj ) {
    if ( obj.hasOwnProperty(prop) ) {
        // prop is actually obj's property (not inherited)
    }
}

(更多关于原型继承的内容)

在数组类型上使用for..in结构的问题在于无法保证属性按什么顺序被产生...而通常来说,这是处理数组的一个非常重要的特征。

另一个问题是它通常比标准的for实现慢。

底线

在数组上使用for…in迭代就像用螺丝刀的臀部去打钉子...你为什么不直接用锤子(for)呢?


4
这是正确的,但避免在数组中使用for ... in还有另一个原因。 来自MDC:_ … for ... in语句将返回用户定义属性的名称以及数字索引 …_ - jball
for...in 结构的特点是它会在原型链上继续搜索可枚举属性。它还会迭代继承的可枚举属性……我对这部分有些模糊,您能否解释一下? - HIRA THAKUR
“速度测试”存在致命缺陷。对象没有长度属性,因此for循环从0到undefined(即它甚至不进入循环)。 - RobG

7
当您想要循环遍历对象的属性时,应使用for...in。但是它与普通的 for 循环相同:循环变量包含当前的“索引”,即对象的属性而非值。
要迭代数组,应该使用普通的 for 循环。 buttons 不是数组,而是类似数组的 NodeList 结构。
如果您要使用for...in 遍历 buttons,可以这样做:
for(var i in a) {
    console.log(i)
}

你会看到它输出类似以下内容:
1
2
...
length
item

因为lengthitemNodeList类型对象的两个属性。因此,如果您简单地使用for..in,您将尝试访问buttons ['length'] .removeAttribute(),这将抛出错误,因为buttons ['length']是一个函数而不是DOM元素。

所以正确的方法是使用普通的for循环。但还有另一个问题:

NodeList是动态的,这意味着每当您访问例如length时,列表都会更新(重新搜索元素)。因此,应避免不必要的length调用。

示例:

for(var i = 0, l = buttons.length; i < l, i++)

2
为了避免调用length,我更喜欢使用for (var i = 0; i in buttons; i++)。我喜欢这种明确的检查方式,而不是隐式地根据长度进行检查。 - gilly3
1
@gilly3:实际上我更喜欢使用for(var i = buttons.length; i--; ),但有时顺序很重要。而且我可以想象i in buttons仍会重新评估列表(除了它可能比数字比较慢之外)。 - Felix Kling

0

for(var key in obj) { } 遍历对象中的所有元素,包括其原型中的元素。因此,如果您使用它并且不知道任何扩展的 Object.prototype,则应始终测试 obj.hasOwnProperty(key) 并跳过该键,如果此检查返回 false。

for(start; continuation; loop) 是 C 风格的循环:start 在循环之前执行,continuation 被测试,只有在它为 true 时循环才会继续,loop 在每次循环后执行。


0

虽然for..in不应该通常用于数组,但在ES5之前,使用它处理稀疏数组是有道理的。

正如其他答案中所指出的,for..in和数组的主要问题是:

  1. 属性的返回顺序不一定(即不是0、1、2等)
  2. 返回所有可枚举的属性,包括非索引属性和那些在[[Prototype]]链上的属性。这会导致性能降低,因为可能需要进行hasOwnProperty测试以避免继承属性。

在ES5之前使用for..in的一个原因是提高稀疏数组的性能,前提是顺序不重要。例如,在以下示例中:

var a = [0];
a[1000] = 1;

使用for..in迭代a将比使用for循环快得多,因为它只会访问两个属性,而for循环将尝试1001次。

然而,ES5的forEach使这种情况变得多余,它只访问存在的成员,因此:

a.forEach();

也只会按顺序迭代两个属性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接