如何正确遍历 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的主要内容,如果未能解决你的问题,请参考以下文章