getElementsByClassName 与 querySelectorAll

IT技术 javascript
2021-02-26 09:34:54

如果我使用

var temp = document.querySelectorAll(".class");
for (var i=0, max=temp.length; i<max; i++) { 
 temp[i].className = "new_class";
}

一切正常。所有节点都会更改它们的类。但是,使用 gEBCN:

var temp = document.getElementsByClassName("class");
for (var i=0, max=temp.length; i<max; i++) { 
 temp[i].className = "new_class";
}  

我得到错误。代码在某个时候跳出循环,没有完成带有 msg“can't set className of null”的工作。
我知道这是静态与实时节点列表问题(我认为),但是由于 gEBCN 速度要快得多,而且我需要遍历巨大的节点列表(树),我真的很想使用 getElementsByClassName。
我能做些什么来坚持使用 gEBCN 而不会被迫使用 querySelectorAll?

2个回答

那是因为HTMLCollection返回的getElementsByClassName是 live。

这意味着如果你添加"class"到某个元素的 classList,它会神奇地出现在temp.

反之亦然:如果您删除"class"内部元素temp,它将不再存在。

因此,更改类会重新索引集合并更改其长度。所以问题是你预先迭代它来捕捉它的长度,而不考虑索引的变化。

为避免此问题,您可以:

  • 使用非实时集合。例如,

    var temp = document.querySelectorAll(".class");
    
  • 将实时转换为HTMLCollection数组。例如,使用其中之一

    temp = [].slice.call(temp);
    temp = Array.from(temp); // EcmaScript 6
    
  • 向后迭代。例如,请参阅@Quentin 的回答

  • 考虑指数的变化。例如,

    for (var i=0; i<temp.length; ++i) { 
     temp[i].className = "new_class";
     --i; // Subtract 1 each time you remove an element from the collection
    }
    
    while(temp.length) { 
     temp[0].className = "new_class";
    }
    
@Quentin 是的,我得到了奇怪的结果。一些节点改变了类,一些没有。
2021-04-20 09:34:54
(在您编辑之前,并且仍然与问题是缓存的声明相关max):这行不通。它会阻止它运行到最后,但它会在运行时跳过元素。即它将修改 index 中的第一个元素0(使其从 NodeList 中删除),然后它将修改刚刚移动到 index第三个元素(跳过移动到 index1的第二个元素0)。
2021-05-01 09:34:54
@Oriol 我认为后向循环是克服 livelist 问题的最优雅和最快的方法。我已经接受了昆汀的回答(他更快)。关于querySelectorAll,使用起来要慢得多,我有千个节点的treeView。gEBCN 规则。
2021-05-02 09:34:54

向后循环遍历列表,然后元素将从末尾消失(您不再看的地方)。

for (var i = temp.length - 1; i >= 0; i--) { 
  temp[i].className = "new_class";
}  

但是请注意,IE 8 支持querySelectorAll但不支持getElementsByClassName,因此您可能更喜欢 querySelectorAll 以获得更好的浏览器支持。


或者,不要删除现有的类:

for (var i=0, max=temp.length; i<max; i++) {  
  temp[i].className += " new_class";
}  
谢谢你。向后循环工作正常:)。我会在 5 分钟内接受答案。
2021-05-20 09:34:54