如何正确遍历 getElementsByClassName

Posted

技术标签:

【中文标题】如何正确遍历 getElementsByClassName【英文标题】:How to correctly iterate through getElementsByClassName 【发布时间】:2013-03-28 10:31:11 【问题描述】:

我是 javascript 初学者。

我正在通过window.onload 启动网页,我必须通过它们的类名 (slide) 找到一堆元素,并根据某些逻辑将它们重新分配到不同的节点中。我有函数Distribute(element),它将一个元素作为输入并进行分配。我想做这样的事情(例如here 或here):

var slides = getElementsByClassName("slide");
for(var i = 0; i < slides.length; i++)

   Distribute(slides[i]);

然而这对我来说并没有什么魔力,因为getElementsByClassName实际上并没有返回数组,而是一个NodeList,也就是......

...这是我的猜测...

...在函数Distribute 内被改变(DOM 树在这个函数内被改变,并且某些节点的克隆发生)。 For-each 循环结构也无济于事。

可变幻灯片的行为确实是不确定的,通过每次迭代它都会疯狂地改变它的长度和元素的顺序。

在我的情况下,迭代 NodeList 的正确方法是什么?我正在考虑填充一些临时数组,但不知道该怎么做......

编辑:

我忘记提到的重要事实是,可能有一张幻灯片在另一张幻灯片中,这实际上改变了slides 变量,因为我刚刚发现感谢用户Alohci。

我的解决方案是先将每个元素克隆到一个数组中,然后将数组逐个传递给Distribute()

【问题讨论】:

这实际上是这样做的,所以你一定是在搞砸别的东西! Distribute() 函数要复制到这里很长而且很复杂,但我确信我正在改变里面的 DOM 结构,我也在那里复制(克隆)元素。调试的时候可以看到变量slides每次传入里面都会发生变化。 它不会改变,除非你真的在某个地方改变它。 我相信getElementsByClassName() 会返回一个实时的nodeList,因此在添加具有该类的元素时,您正在迭代更改的nodeList 的长度。 @Kupto- 反向循环通常可以解决此类问题,其中 Distribute 函数删除或移动元素,使其不再匹配 getElementsByClassName 调用,原因是 David Thomas 给出的。跨度> 【参考方案1】:

According to MDN,从NodeList 中检索项目的方法是:

nodeItem = nodeList.item(index)

因此:

var slides = document.getElementsByClassName("slide");
for (var i = 0; i < slides.length; i++) 
   Distribute(slides.item(i));

我自己没有尝试过(正常的for 循环一直对我有用),但试一试。

【讨论】:

这是正确的解决方案,除非您尝试查找并更改具有相同类且彼此内部的元素。我在编辑我的问题时解释了我的解决方法。 当然,没有考虑到这一点。 如果我可以问,为什么会这样?为什么没有实现它以便您能够像for(var el in document.getElementsByClassName("foo")) 这样迭代节点? for ... of 允许您现在像 for (slide of slides) Distribute(slide) 一样遍历 NodeList。浏览器支持不完整,但如果您正在转译,则 for ... of 将被转换,但 NodeList.forEach 不会。【参考方案2】:

如果你使用新的 querySelectorAll,你可以直接调用 forEach。

document.querySelectorAll('.edit').forEach(function(button) 
    // Now do something with my button
);

根据下面的评论。 nodeLists 没有 forEach 函数。

如果将它与 babel 一起使用,您可以添加 Array.from,它会将非节点列表转换为 forEach 数组。 Array.from 在以下浏览器(包括 IE 11)中无法原生运行。

Array.from(document.querySelectorAll('.edit')).forEach(function(button) 
    // Now do something with my button
);

在昨晚的聚会上,我发现了另一种处理没有 forEach 的节点列表的方法

[...document.querySelectorAll('.edit')].forEach(function(button) 
    // Now do something with my button
);

Browser Support for [...]

显示为节点列表

显示为数组

【讨论】:

问题在于 nodeLists 在每个浏览器中都没有 forEach 函数。如果你愿意修改原型,这很简单:if ( !NodeList.prototype.forEach ) NodeList.prototype.forEach = Array.prototype.forEach; 如果我将您的答案与@joshcanhelp 的评论结合起来,这是一个优雅的解决方案。谢谢 :) 当然,这只会导致多循环的线路优势。 您应该避免这种情况,因为它可能不适用于所有浏览器。这是我使用的一个简单的解决方法,似乎在任何地方都能完美运行:css-tricks.com/snippets/javascript/… 我想你的意思是[...document.getElementsByClassName('.edit')].forEach(function(button) @wp-overwatch.com 类名中不需要点。正确的版本应该是:[...document.getElementsByClassName('edit')].forEach(function(button) 【参考方案3】:

2021 年的最新答案

当这个问题被问到时(2013 年),.getElementsBy* 方法返回了一个 NodeList。但是,2021 年的情况并非如此,所有这些 DOM 遍历方法都会返回一个实时的htmlCollection,getElementsByName 是一个例外。

这两个列表之间存在显着差异。 HTMLCollection 有两个方法,NodeList 有五个方法,包括NodeList.forEach,可用于遍历NodeList。

实时收藏存在问题,因为无法在后台保持收藏更新。为了实现可靠的集合,在 HTMLCollection 的每个当前实现中,每次访问集合时都会遍历 DOM。实际上,这意味着每次访问实时集合的成员(包括长度)时,浏览器都会遍历整个文档以查找特定元素。

The Standard 说:

如果一个集合是实时的,那么该对象上的属性和方法必须对实际的底层数据进行操作,而不是数据的快照。

永远不要迭代实时 HTMLCollection!

相反,将集合转换为数组,然后迭代该数组。或者更确切地说,使用 .querySelectorAll 获取元素,它为您提供静态 NodeList 和更灵活的选择元素的方式。

如果您确实需要元素的实时列表,请使用最接近的共同祖先元素作为上下文,而不是 document

值得注意的是,还存在实时 NodeList。实时 NodeList 的示例是 Node.childNodes 和 getElementsByName 的返回值。

【讨论】:

所以你的意思是不要在 getElementsBy* 上使用 for 循环? @Nomentsoa 是的,完全正确,或者任何循环。【参考方案4】:

你总是可以使用数组方法:

var slides = getElementsByClassName("slide");
Array.prototype.forEach.call(slides, function(slide, index) 
    Distribute(slides.item(index));
);

【讨论】:

非常漂亮漂亮的回答,非常感谢! @lesolorzanov 'Distribute' 是在其源代码中创建的帖子所有者的自定义函数。请也阅读问题。【参考方案5】:

我遵循Alohci 的建议反向循环,因为它是实时的nodeList。这是我为那些好奇的人所做的......

  var activeObjects = documents.getElementsByClassName('active'); // a live nodeList

  //Use a reverse-loop because the array is an active NodeList
  while(activeObjects.length > 0) 
    var lastElem = activePaths[activePaths.length-1]; //select the last element

    //Remove the 'active' class from the element.  
    //This will automatically update the nodeList's length too.
    var className = lastElem.getAttribute('class').replace('active','');
    lastElem.setAttribute('class', className);
  

【讨论】:

【参考方案6】:
 <!--something like this--> 
<html>
<body>



<!-- i've used for loop...this pointer takes current element to apply a 
 particular change on it ...other elements take change by else condition 
-->  


<div class="classname" onclick="myFunction(this);">first</div>  
<div class="classname" onclick="myFunction(this);">second</div>


<script>
function myFunction(p) 
 var x = document.getElementsByClassName("classname");
 var i;
 for (i = 0; i < x.length; i++) 
    if(x[i] == p)
    
x[i].style.background="blue";
    
    else
x[i].style.background="red";
    




</script>
<!--this script will only work for a class with onclick event but if u want 
to use all class of same name then u can use querySelectorAll() ...-->




var variable_name=document.querySelectorAll('.classname');
for(var i=0;i<variable_name.length;i++)
variable_name[i].(--your option--);




 <!--if u like to divide it on some logic apply it inside this for loop 
 using your nodelist-->

</body>
</html>

【讨论】:

【参考方案7】:

我在迭代中遇到了类似的问题,我来到了这里。也许其他人也犯了同样的错误。

就我而言,选择器根本不是问题。问题是我弄乱了javascript代码: 我有一个循环和一个子循环。子循环也使用i 作为计数器,而不是j,所以因为子循环覆盖了主循环的i 的值,所以这个循环从未进入第二次迭代。

var dayContainers = document.getElementsByClassName('day-container');
for(var i = 0; i < dayContainers.length; i++)  //loop of length = 2
        var thisDayDiv = dayContainers[i];
        // do whatever

        var inputs = thisDayDiv.getElementsByTagName('input');

        for(var j = 0; j < inputs.length; j++)  //loop of length = 4
            var thisInput = inputs[j];
            // do whatever

        ;

    ;

【讨论】:

【参考方案8】:

您可以使用Object.values + for...of 循环:

const listA = document.getElementById('A');
const listB = document.getElementById('B');
const listC = document.getElementById('C');
const btn = document.getElementById('btn');

btn.addEventListener('click', e => 
  // Loop & manipulate live nodeLList
  for (const li of Object.values(listA.getElementsByClassName('li'))) 
    if (li.classList.contains('active')) 
      listB.append(li);
     else 
      listC.append(li);
    
  
);
ul 
  display: inline-flex;
  flex-direction: column;
  border: 1px solid;


ul::before 
  content: attr(id);


.active 
  color: red;


.active::after 
  content: " (active)";
<ul id="A">
  <li class="li active">1. Item</li>
  <li class="li">2. Item</li>
  <li class="li">3. Item</li>
  <li class="li active">4. Item</li>
  <li class="li active">5. Item</li>
  <li class="li">6. Item</li>
  <li class="li active">7. Item</li>
  <li class="li">8. Item</li>
</ul>

<button id="btn">Distribute A</button>

<ul id="B"></ul>
<ul id="C"></ul>
单线:
Object.values(listA.getElementsByClassName('li')).forEach(li => (li.classList.contains('active') ? listB : listC).append(li))

【讨论】:

以上是关于如何正确遍历 getElementsByClassName的主要内容,如果未能解决你的问题,请参考以下文章

GetElementsByClass名称

用原生JS实现getElementsByClass

前端编程中小错误总结

如何正确遍历 getElementsByClassName

C 函数中的变量参数列表 - 如何正确遍历 arg 列表?

Java HashMap 如何正确遍历并删除元素