我正在尝试使用JavaScript单击以从元素中删除所有隐藏的类。这是我用来尝试执行此操作的伪代码:
<style>
.hidden {display:none;}
</style>
<div>Value 1</div>
<div class="hidden">Value 2</div>
<div class="hidden">Value 3</div>
<div class="hidden">Value 4</div>
<button onclick="removeHidden()">Show All</button>
<script>
function removeHidden()
{
var hidden = document.getElementsByClassName("hidden");
for(var i=0; i<hidden.length; i++)
{
hidden[i].classList.remove("hidden");
}
}
</script>
单击按钮时,我希望所有“隐藏”类都将被删除,但是奇怪的是,它从第二个div和第四个div中删除了隐藏的类,但跳过了第三个。
我得到的结果是:
Value 1
Value 2
Value 4
有人知道为什么那是因为我真的不明白这一点吗?
我也尝试了这段代码,但结果相同:
var els = document.getElementsByClassName("hidden");
Array.prototype.forEach.call(els, function(el) {
el.ClassList.remove("hidden");
});
答案 0 :(得分:5)
问题是getElementsByClassName()
返回一个"live" node list,这是一个列表,只要您引用该列表,它就会更新。这样可以确保您始终获得最新的元素引用。这是一个昂贵的构造,实际上仅在需要时用于少数用例。
每次您的代码都引用hidden
变量,将重新扫描DOM以查找具有hidden
类的元素,并在您开始删除该类后,将其长度清单中的清单减少了一个。正是由于length
的这种变化,一项被跳过了。
要在此处正确使用getElementsByClassName()
,请先从最后一个元素中删除该类,然后逐步返回第一个元素。这样可以确保随着节点列表长度的缩短,您不会跳过任何节点。
<style>
.hidden {display:none;}
</style>
<div>Value 1</div>
<div class="hidden">Value 2</div>
<div class="hidden">Value 3</div>
<div class="hidden">Value 4</div>
<button onclick="removeHidden()">Show All</button>
<script>
function removeHidden()
{
var hidden = document.getElementsByClassName("hidden");
for(var i = hidden.length-1; i > -1; i--)
{
hidden[i].classList.remove("hidden");
}
}
</script>
但是,由于活动节点列表会导致性能下降,因此通常不要使用它们。而是使用静态节点列表,您可以使用更现代,更灵活的.querySelectorAll()
获得该列表。另外,如果我们将.querySelectorAll()
返回的静态节点列表转换为Array,则可以使用Array API通过.forEach()
对其进行迭代,从而无需索引器。
<style>
.hidden {display:none;}
</style>
<div>Value 1</div>
<div class="hidden">Value 2</div>
<div class="hidden">Value 3</div>
<div class="hidden">Value 4</div>
<button onclick="removeHidden()">Show All</button>
<script>
function removeHidden()
{
// Get all the elements that match the selector into an Array
var hidden = Array.prototype.slice.call(document.querySelectorAll(".hidden"));
// Now we can loop using the Array API
hidden.forEach(function(item){
item.classList.remove("hidden");
});
}
</script>
答案 1 :(得分:2)
您可以使用querySelectorAll
。 getElementsByClassName
的问题在于它生成的列表是dynamic
。这意味着如果对change
进行了一些DOM
,则instantly
反映在list
中,因为无论何时访问列表,都会扫描DOM以提供列表。因此,在循环中,classes are removed
与列表中的length
一一对应,而循环中使用的列表grew shorter
也i<hidden.length
。 querySelectorAll
提供了static list
从而提供了正确的输出。
function removeHidden() {
var hidden = document.querySelectorAll(".hidden");
for (var i = 0; i < hidden.length; i++) {
hidden[i].classList.remove("hidden");
}
}
.hidden {
display: none;
}
<div>Value 1</div>
<div class="hidden">Value 2</div>
<div class="hidden">Value 3</div>
<div class="hidden">Value 4</div>
<button onclick="removeHidden()">Show All</button>
答案 2 :(得分:1)
原因是您要迭代的列表是“实时列表”。这意味着它始终反映DOM的当前状态。这样,当您使用该类从DOM中删除元素时,该元素也会从列表中删除。
删除列表后,将从该点开始重新编制索引,这意味着当前迭代现在指向 next 元素。递增i++
后,您将跳过该元素,然后移至下一个元素,该元素以前是前面的两个元素。在您遍历列表时,此操作继续进行。
要解决此问题,请从列表末尾开始迭代,或使用非活动列表进行迭代。
答案 3 :(得分:0)
改为使用var hidden = document.querySelectorAll(".hidden")
编辑:正如Ziggy Wiggy解释的那样,这是因为您遍历DOM元素列表,一旦删除一个,其余的元素实质上就被“移位”或向下索引一个位置。因此,当您遍历值2并将其删除时,值3成为列表中的第一个元素,并且您已经对其进行了遍历,因此循环跳过了值3并转到了值4。为避免这种情况,querySelector种提供了一种DOM元素的快照。
工作片段:
<style>
.hidden {
display: none;
}
</style>
<div>Value 1</div>
<div class="hidden">Value 2</div>
<div class="hidden">Value 3</div>
<div class="hidden">Value 4</div>
<button onclick="removeHidden()">Show All</button>
<script>
function removeHidden() {
var hidden = document.querySelectorAll(".hidden");
for (var i = 0; i < hidden.length; i++) {
hidden[i].classList.remove("hidden");
}
}
</script>