从DOM中删除HTMLCollection元素

时间:2014-06-02 07:06:37

标签: javascript dom

我有一系列段落元素。有些是空的,有些只包含空格,有些则有内容:

<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>
<p></p>
<p> </p>
<p>    </p>
<p>&nbsp;</p>
<p> &nbsp;</p>

我使用getElementsByTagName选择它们:

var paragraphs = document.getElementsByTagName('p');

这将返回文档中的所有段落。我想删除所有这些,所以我想要运行

for (var i = 0, len = paragraphs.length; i < len; i++) {
   paragraphs[i].remove();
}

但我收到Uncaught TypeError: Cannot read property 'remove' of undefined错误。我觉得这很奇怪,但我想尝试添加一名后卫,看看会发生什么:

for (var i = 0, len = paragraphs.length; i < len; i++) {
   paragraphs[i] && paragraphs[i].remove();
}

没有错误,但并非所有元素都被删除。所以我再次运行它,它删除了以前没有删除过的部分元素。我再次运行 最后所有段落都从文档中删除。

我想知道我在这里错过了哪些明显的细节。

Demo of the problem

5 个答案:

答案 0 :(得分:22)

问题是paragraphs实时列表。通过删除p元素,您也可以更改该列表。一个简单的解决方法是在逆序中迭代列表:

for (var i = paragraphs.length - 1; i >= 0; --i) {
  paragraphs[i].remove();
}

另一种解决方案是创建静态列表(非实时列表)。您可以通过以下任一方式执行此操作:

  • 将列表转换为Array

    var paragraphs =
      Array.prototype.slice.call(document.getElementsByTagName('p'), 0);
    
  • 使用document.querySelectorAll

    var paragraphs = document.querySelectorAll('p');
    

然后,您可以按常规顺序遍历列表(使用for循环):

for (var i = 0; i < paragraphs.length; ++i) {
  paragraphs[i].remove();
}

或(使用for...of循环):

for (var paragraph of paragraphs) {
  paragraph.remove();
}

请注意,.remove是一种相对较新的DOM方法,并不是每个浏览器都支持。有关详细信息,请参阅MDN documentation


为了说明问题,让我们假设我们有一个包含三个元素的节点列表paragraphs = [p0, p1, p2]。然后,当您遍历列表时会发生这种情况:

i = 0, length = 3, paragraphs[0] == p0  => paragraphs = [p1, p2]
i = 1, length = 2, paragraphs[1] == p2  => paragraphs = [p1]
i = 2, length = 1, END

因此,在此示例中,p1未被删除,因为它被跳过。

答案 1 :(得分:7)

删除项目时,HTMLCollection的长度会发生变化。一种方法是使用while循环

while(paragraphs.length > 0) {
   paragraphs[0].remove();
}

答案 2 :(得分:1)

为什么这对您不起作用:

当您删除节点时, HTMLCollection 正在变异(更改),其长度与的“实际”长度成为不同步 HTMLCollection 数组。

假设您有2个DOM节点的数组,并且正在对其进行迭代。它应该迭代2次。下面的演示完美地说明了这一点,我很容易理解:

第一次迭代-删除第一个节点,然后递增i
第二次迭代-现在i等于1,但是paragraphs.length现在也1,因为此时只剩下一个段落。

这将导致一个不可能的情况:要求长度为1的数组访问位置1处的项目,并且唯一可用的位置是0(因为Arrays 从位置0开始 ...)

访问数组(或类似数组的对象HTMLCollection)中不存在的位置是非法的。

var paragraphs = document.getElementsByTagName('p')

for (var i = 0; i <= paragraphs.length; i++) {
   console.log(i, paragraphs.length)
   paragraphs[i].remove()
}
<p>1</p>
<p>2</p>

可能的解决方案:延迟节点的移除

在下面的演示中,在完成所有迭代周期后{em> setTimeout延迟了代码执行),删除了节点。是利用第三个参数并传递将被缓存为超时回调参数的节点:

var paragraphs = document.getElementsByTagName('p')

for (var i = 0, len = paragraphs.length; i < len; i++) {
   setTimeout(node => node.remove(),0 , paragraphs[i])
}
<p>Pellentesque habitant....</p>
<p></p>
<p> </p>
<p>    </p>
<p>&nbsp;</p>
<p> &nbsp;</p>

可能的解决方法:在每次迭代中检查节点是否存在

另外,重要的是不要递增i,因为数组的长度一直在缩小,每次迭代都会删除第一项,直到不再剩余任何项为止

var paragraphs = document.getElementsByTagName('p')

for (var i = 0, len = paragraphs.length; i < len; ) {
   if(  paragraphs[i] )
     paragraphs[i].remove()
}
<p>1</p>
<p>2</p>
<p>3</p>

可能的解决方法:我几乎总是喜欢反向迭代器

var paragraphs = document.getElementsByTagName('p')

for (var i = paragraphs.length; i--; ){
    paragraphs[i].remove() // could also use `paragraphs[0]`. "i" index isn't necessary
}
<p>1</p>
<p>2</p>
<p>3</p>

答案 3 :(得分:0)

您必须在其父级上使用removeChild来执行此操作:

for (var i = 0, len = paragraphs.length; i < len; i++) {
    paragraphs[i].parentNode.removeChild(paragraphs[i]);
}

编辑:plnkr似乎从这里运行不好,所以我没有测试,但它应该工作:

window.addEventListener("load", function() {
    var paragraphs = document.getElementsByTagName('p');

    var loop = function() {
        for (var i = 0, len = paragraphs.length; i < len; i++) {
            paragraphs[i].parentNode.removeChild(paragraphs[i]);
        }
    };

    console.log(paragraphs.length) // 7
    loop();
    console.log(paragraphs.length) // 3
    loop();
    console.log(paragraphs.length) // 1
    loop();
    console.log(paragraphs.length) // 0
});

答案 4 :(得分:0)

就我而言,这是我的解决方案:

var temp = document.getElementsByClassName('level-size');
for (var i = 0, len = temp.length; i < len; i++)
    temp[0].remove();

temp [0] 因为每次删除它都会将孔表推回一个索引。