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

我是 Javascript 初学者。

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

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 给出的。跨度>

标签: javascript html dom


【解决方案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 遍历方法都会返回一个实时的HTMLCollectiongetElementsByName 是一个例外。

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

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

The Standard 说:

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

永远不要迭代实时 HTMLCollection!

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

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

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

【讨论】:

  • 所以你的意思是不要在 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))
        

        【讨论】:

          猜你喜欢
          • 2022-12-01
          • 2012-03-20
          • 2017-12-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-01-13
          相关资源
          最近更新 更多