从getElementsByClassName获取到的HTMLCollection迭代时表现奇怪

14

我写了一个函数来改变元素的类,从而改变它们的属性。但由于某些原因,只有部分元素发生了变化。我花了几个小时才找到解决方案,但这对我来说似乎很奇怪。也许你可以向我解释一下。

这不起作用:

function replace(){
  var elements = document.getElementsByClassName('classOne');

  for (var i = 0; i < elements.length; i++) {
    elements[i].className = 'classTwo';               
  }
}

查看JSFiddle:只有每隔一个项目才会受到影响;只有每隔一个红色元素才会改变颜色为蓝色。

因此,我将for循环的最终表达式更改为不再递增i

function replace(){
  var elements = document.getElementsByClassName('classOne');

  for (var i = 0; i < elements.length; i) { // Here’s the difference
    elements[i].className = 'classTwo';               
  }
}

这个方法运行良好!看起来push被调用时不需要增量。这正常吗?这与我见过的例子不同。


我从未听说过“循环系数”这个术语。JavaScript将for循环的最后一部分称为“final expression”(或在规范的某些上下文中称为“increment”),而i++是一个“increment”(或者通俗地说,是规范中的“UpdateExpression”)。 - Sebastian Simon
相关问题:for...in循环未遍历所有属性? - Sebastian Simon
3个回答

21

发生的是一个奇怪的副作用。当你为每个elements元素重新分配className时,该元素会从数组中移除! (实际上,正如@ user2428118指出的那样,elements是一个类似于数组的对象,而不是一个数组。有关区别,请参见此线程。)这是因为它不再具有classOne类名。当循环退出(在第二种情况下),elements数组将为空。

您可以将循环编写为:

while (elements.length) {
    elements[0].className = 'classTwo'; // removes elements[0] from elements!
}
在你的第一个情况中,通过递增i,你跳过了一半具有classOne类的(原始)元素。
顺便说一句,这是一个非常好的问题。研究充分且清晰易懂。

谢谢!听起来没错,但是这与编程有关吗?“元素”是一个已存在的对象,而不是可以在没有你的情况下更改的动态对象吗? - MeNa
1
@MeNa - 虽然elements变量是局部的,但它所引用的数组也可以被DOM访问和维护。每个元素的className属性实际上会触发DOM机制内的setter函数。该函数的一个副作用是从elements引用的数组中删除该元素。因此,它不是在没有你的情况下改变;它是因为你改变了其中一个元素的className属性而改变的。引用古代智者的话:“这不是一个错误;这是一个特性!” - Ted Hopp
@TedHopp - 再次感谢你!现在一切都清楚了。(顺便说一句,感谢你提供的'while()',这是一个漂亮而优雅的解决方案。) - MeNa
你可能应该提到,elements 实际上不是一个数组,而只是一种类似数组的数据结构。 - user2428118

4

getElementsByClassName 返回一个 NodeList。NodeList 集合是一个实时集合,这意味着文档的修改会影响该集合。更多信息


谢谢。确实,“实时集合”这个术语很好地解释了这一点。 - MeNa
4
并非所有的NodeList集合都是动态的。一些"获取"方法例如querySelectorAll()会返回静态的NodeList,它们不会受到后续DOM更改的影响。 - SHH
它返回一个活动的HTMLCollection - pilchard

-3

或者将循环反转,从长度-1开始向下步进到0


是的,那也可以解决问题,但问题需要的是解释而不是解决方案。 - Bergi

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