简体   繁体   English

使用 JavaScript 从每个元素中删除隐藏的类

[英]Remove hidden class from every element with JavaScript

I'm trying to remove all hidden classes from elements on click with JavaScript.我正在尝试使用 JavaScript 从元素中删除所有隐藏的类。 Here is the dummy code I use to try doing this:这是我用来尝试执行此操作的虚拟代码:

 <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>

When clicking the button, I would expect all classes 'hidden' to be removed but oddly, it removes the hidden class from the second div and from the fourth but skips the third.单击按钮时,我希望所有“隐藏”的类都被删除,但奇怪的是,它从第二个 div 和第四个 div 中删除了隐藏的类,但跳过了第三个。

The result I get is:我得到的结果是:

Value 1
Value 2
Value 4

Any idea why that is because I really don't understand this?知道为什么这是因为我真的不明白这一点吗?

I also tried this code but with the same result:我也试过这段代码,但结果相同:

var els = document.getElementsByClassName("hidden");

Array.prototype.forEach.call(els, function(el) {
    el.ClassList.remove("hidden");
});

The issue is that getElementsByClassName() returns a "live" node list , which is a list that is updated anytime you reference the list.问题在于getElementsByClassName()返回一个“实时”节点列表,该列表在您引用该列表时随时更新。 This ensures that you get the most up to date element references at all times.这可确保您始终获得最新的元素引用。 This is an expensive construct and is really only for rare use cases when it is needed.这是一种昂贵的构造,实际上仅适用于需要时的罕见用例。

Every time your code references the hidden variable, the DOM is re-scanned for elements that have the hidden class and after you begin removing that class, the length of the list shrinks by one.每次您的代码引用hidden变量时,都会重新扫描 DOM 以查找具有hidden类的元素,并且在您开始删除该类后,列表的长度会缩小 1。 It's because of this changing length that one item gets skipped.正是由于这种不断变化的length ,才跳过了一项。

To use getElementsByClassName() correctly here, remove the class from the last element first and work your way backward to the first.要在此处正确使用getElementsByClassName() ,请先从最后一个元素中删除该类,然后再回到第一个元素。 This ensures that as the length of the node list shrinks, you are not skipping over any nodes.这确保随着节点列表的长度缩小,您不会跳过任何节点。

 <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>

But, because live node lists cause a performance hit, as a general rule, don't use them.但是,因为活动节点列表会导致性能下降,所以一般来说,不要使用它们。 Instead, use a static node list, which you get with the more modern and more flexible .querySelectorAll() .相反,使用静态节点列表,您可以获得更现代和更灵活的.querySelectorAll() Also, if we convert the static node list returned by .querySelectorAll() into an Array, we can use the Array API to iterate it with .forEach() , which eliminates the need for an indexer.此外,如果我们将.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>

You can use querySelectorAll .您可以使用querySelectorAll The problem with getElementsByClassName is that the list it generates is dynamic . getElementsByClassName的问题在于它生成的列表是dynamic It means that if some change is made to the DOM it is instantly reflected in the list , because whenever the list is accessed the DOM is scanned to provide the list.这意味着如果对DOM进行了一些change ,它会instantly反映在list ,因为每当访问list ,都会扫描 DOM 以提供列表。 So when in the loop the classes are removed one by one the length of the list also grew shorter which was used in the loop( i<hidden.length ).因此,当在循环中classes are removed一个一个classes are removed时,列表的lengthgrew shorter了,循环中使用了它( i<hidden.length )。 While querySelectorAll provides a static list thus gives the correct output.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>

The reason is that the list you're iterating is a "live list".原因是您要迭代的列表是“实时列表”。 This means that it reflects the current state of the DOM at all times.这意味着它始终反映 DOM 的当前状态。 As such, when you remove an element from the DOM with that class, it also gets removed from the list.因此,当您使用该类从 DOM 中删除元素时,它也会从列表中删除。

After its removal, the list is re-indexed from that point forward, meaning that the current iteration is now pointing to what had been the next element.删除后,列表从该点开始重新索引,这意味着当前迭代现在指向下一个元素。 Upon incrementing i++ , you've skipped over that element, and moved on to the next, which had previously been two elements ahead.在递增i++ ,您已经跳过了该元素,并移到了下一个元素,该元素以前是两个元素。 This continues on as you iterate the list.当您迭代列表时,这会继续进行。

To solve it, either iterate from the end of the list to the start, or use a non-live-list for iteration.要解决它,要么从列表的末尾迭代到开头,要么使用非活动列表进行迭代。

use var hidden = document.querySelectorAll(".hidden") instead使用var hidden = document.querySelectorAll(".hidden")代替

EDIT: as Ziggy Wiggy explained this is because your iterating over a list of DOM elements, once you remove one the rest of the elements are essentially "shifted" or re-indexed down one position.编辑:正如 Ziggy Wiggy 解释的那样,这是因为您对 DOM 元素列表进行了迭代,一旦您删除了一个元素,其余元素基本上会“移动”或重新索引到一个位置。 So when you iterate through value 2 and remove it, value 3 becomes the first element in the list, and you already iterated over it so the loop skips value 3 and goes to value 4. To avoid this, querySelector kind of provides a sort of snapshot of DOM Elements.因此,当您遍历值 2 并删除它时,值 3 成为列表中的第一个元素,并且您已经对其进行了迭代,因此循环会跳过值 3 并转到值 4。为了避免这种情况,querySelector 提供了一种DOM 元素的快照。

working snippet:工作片段:

 <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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM