JavaScript中移除DOM节点的所有子元素

1347

我该如何使用JavaScript删除DOM节点的所有子元素?

假设我有以下(丑陋的)HTML代码:

<p id="foo">
    <span>hello</span>
    <div>world</div>
</p>

我这样获取所需节点:

var myNode = document.getElementById("foo");

我如何移除foo的子元素,以便只剩下<p id="foo"></p>

我可以这样做吗:

myNode.childNodes = new Array();

我应该使用removeElement的某种组合吗?

我希望答案直接涉及DOM;如果您提供了纯DOM答案以及jQuery答案,则会获得额外加分。

39个回答

2292

选项1 A: 清除 innerHTML

  • 这种方法简单,但可能不适用于高性能应用程序,因为它会调用浏览器的HTML解析器(尽管浏览器可能优化值为空字符串的情况)。

doFoo.onclick = () => {
  const myNode = document.getElementById("foo");
  myNode.innerHTML = '';
}
<div id='foo' style="height: 100px; width: 100px; border: 1px solid black;">
  <span>Hello</span>
</div>
<button id='doFoo'>Remove via innerHTML</button>

选项1 B: 清除textContent

  • 与上面相同,但使用.textContent。根据MDN的说明,这将比innerHTML更快,因为浏览器不会调用它们的HTML解析器,而是立即用单个#text节点替换元素的所有子级。

doFoo.onclick = () => {
  const myNode = document.getElementById("foo");
  myNode.textContent = '';
}
<div id='foo' style="height: 100px; width: 100px; border: 1px solid black;">
  <span>Hello</span>
</div>
<button id='doFoo'>Remove via textContent</button>

选项2 A:循环删除每个lastChild

  • 这个答案的早期版本使用了firstChild,但是更新为使用lastChild,因为在计算机科学中,通常从集合中删除最后一个元素比删除第一个元素快得多(具体取决于集合的实现方式)。
  • 循环继续检查firstChild以防万一它比lastChild更快(例如,如果UA将元素列表实现为定向链表)。

doFoo.onclick = () => {
  const myNode = document.getElementById("foo");
  while (myNode.firstChild) {
    myNode.removeChild(myNode.lastChild);
  }
}
<div id='foo' style="height: 100px; width: 100px; border: 1px solid black;">
  <span>Hello</span>
</div>
<button id='doFoo'>Remove via lastChild-loop</button>

选项二B:循环删除每个lastElementChild

  • 这种方法保留了父元素的所有非Element类型的子节点(即#text文本节点和<!-- comments -->注释节点),但不包括它们的后代节点,这在应用程序中可能是可取的(例如某些使用内联HTML注释存储模板指令的模板系统)。
  • 这种方法直到近年才被广泛采用,因为Internet Explorer直到IE9才添加了对lastElementChild的支持。

doFoo.onclick = () => {
  const myNode = document.getElementById("foo");
  while (myNode.lastElementChild) {
    myNode.removeChild(myNode.lastElementChild);
  }
}
<div id='foo' style="height: 100px; width: 100px; border: 1px solid black;">
  <!-- This comment won't be removed -->
  <span>Hello <!-- This comment WILL be removed --></span>
  <!-- But this one won't. -->
</div>
<button id='doFoo'>Remove via lastElementChild-loop</button>

奖励: Element.clearChildren 猴子补丁:

  • 我们可以在JavaScript中将一个新的方法属性添加到Element原型中,以简化调用它为只需使用el.clearChildren()(其中el是任何HTML元素对象)。
  • (严格来说,这是一种猴子补丁,而不是polyfill,因为这不是标准DOM功能或缺失功能。请注意,在许多情况下,正确地禁止使用猴子补丁。)

if( typeof Element.prototype.clearChildren === 'undefined' ) {
    Object.defineProperty(Element.prototype, 'clearChildren', {
      configurable: true,
      enumerable: false,
      value: function() {
        while(this.firstChild) this.removeChild(this.lastChild);
      }
    });
}
<div id='foo' style="height: 100px; width: 100px; border: 1px solid black;">
  <span>Hello <!-- This comment WILL be removed --></span>
</div>
<button onclick="this.previousElementSibling.clearChildren()">Remove via monkey-patch</button>


6
来自 jQuery,可以考虑这两个问题:该方法不仅会删除子元素(和其他后代元素),还会删除匹配元素集合中的任何文本。这是因为根据 DOM 规范,元素内的任何文本字符串都被视为该元素的子节点。"为了避免内存泄漏,jQuery 在删除元素本身之前,会先删除子元素中的数据和事件处理程序等其他结构。" - jottos
35
如果你只涉及 HTML,那么 innerHTML 可以正常使用。但如果其中包含 SVG 等其他元素,则只有元素移除才能生效。 - stwissel
73
永远不要使用innerHTML =''。问题是,虽然节点看起来被移除了,但其实内部的节点仍然存在,会拖慢网页速度。你必须有一个明确的原因来删除所有单独的节点。这在Google Chrome和IE上都进行了测试。请考虑移除innerHTML“解决方案”,因为它是错误的。 - Kenji
6
使用 .remove() 操作甚至更快。 - Joeytje50
44
我不认为Kenji的评论有充分的依据。使用innerHTML = ''会更慢,但我不认为这是“错误”的。任何关于内存泄漏的说法都应该有证据支持。 - Chris Middleton
显示剩余22条评论

478

在2022年及以后,请使用replaceChildren() API!

现在可以使用跨浏览器支持的replaceChildren API来替换所有子元素:

container.replaceChildren(...arrayOfNewChildren);

这将同时完成以下两个操作:

  • 移除所有已存在的子元素,以及
  • 一次性添加所有给定的新子元素。

你也可以使用同样的API只移除已存在的子元素而不替换它们:

container.replaceChildren();

这个功能在Chrome/Edge 86+、Firefox 78+和Safari 14+中得到充分支持。它是完全指定的行为。此方法可能比此处提出的任何其他方法更快,因为它不需要使用innerHTML进行旧子元素的删除和新子元素的添加,并且只需一步即可完成操作。


3
MDN数据已经修正,现在显示完整的跨浏览器支持:https://caniuse.com/?search=replacechildren - Mason Freed
7
很遗憾 @Polaris878 自2018年以来就没有出现过。这应该成为新的被接受的答案。 - spkrtn
2
@ygoe,那不应该是这样的。你能给个例子吗?这是我的示例代码:https://jsbin.com/xopapef,其中使用了一个包含10,000个子元素的目标元素。至少在Chrome中,在两种情况下都只显示一个变异观察器调用。你看到了不同的东西吗?如果是这样,请澄清你正在使用的浏览器和平台。 (对我来说,我的演示还表明replaceChildren()至少与textContent一样快,并且通常更快。) - Mason Freed
2
我在Mac上使用Chrome、Firefox和Safari运行了我上面链接的网站,它们都达成了一致:只有一个变异观察器调用。而且在这三个浏览器中,replaceChildren()的速度略微更快。如果您看到不同的情况,请告诉我。 - Mason Freed
6
已将此更改为被接受的答案,因为它现在是建议的现代方法 :) - Polaris878
显示剩余8条评论

164

使用现代JavaScript,用remove

const parent = document.getElementById("foo")
while (parent.firstChild) {
    parent.firstChild.remove()
}

这是在 ES5 中写节点删除的一种更新方法。它是原生 JS,并且比依赖父节点更好阅读。

所有现代浏览器都支持。

浏览器支持 - 97% 2021年6月


22
由于parent.children/parent.childNodes是活性列表,调用remove会修改列表,因此您的迭代器不能保证迭代所有内容,for循环将不起作用。例如在Chrome中,您最终会跳过每个其他节点。更好的方法是使用while(parent.firstChild){ parent.removeChild(parent.firstChild); }循环。 - qix
4
酷炫的简写,虽然不太易读:while (parent.firstChild && !parent.firstChild.remove()); - Gibolt
3
当性能不是非常重要时,可以使用以下一行代码:[...parent.childNodes].forEach(el => el.remove());,该代码可从父元素中移除所有子元素。 - user993683
3
这个在Typescript里面不起作用... 可以尝试使用 while (selectElem.firstElementChild) { selectElem.firstElementChild.remove(); } 代替。 - John Henckel

140

目前普遍接受的答案关于 innerHTML 比较慢是错误的(至少在IE和Chrome中),正如m93a所正确提到的。

使用这种方法(会破坏已附加的 jQuery 数据),Chrome 和 FF 的速度大大提高:

var cNode = node.cloneNode(false);
node.parentNode.replaceChild(cNode, node);

在Firefox和Chrome中稍次,但在IE中最快。

node.innerHTML = '';

InnerHTML不会破坏您的事件处理程序或中断jquery引用,在这里也建议使用它作为解决方案: https://developer.mozilla.org/en-US/docs/Web/API/Element.innerHTML.

最快的DOM操作方法(仍比前两种方法慢)是范围删除,但直到IE9才支持范围。

var range = document.createRange();
range.selectNodeContents(node);
range.deleteContents();

其他提到的方法似乎可以相互比较,但是除了jquery(1.1.1和3.1.1)之外都比innerHTML慢得多,而jquery则明显比其他任何东西都要慢:

$(node).empty();

证据在这里:

http://jsperf.com/innerhtml-vs-removechild/167 http://jsperf.com/innerhtml-vs-removechild/300 https://jsperf.com/remove-all-child-elements-of-a-dom-node-in-javascript (因为编辑旧网址不起作用,所以这是 jsperf 重启的新网址)

Jsperf 的 "per-test-loop" 经常被理解为 "per-iteration",只有第一次迭代有要删除的节点,因此结果是没有意义的,在发布时,在此线程中存在设置不正确的测试。


4
你的 jsperf 完全失效了,这并不能作为很好的证据。 - bryc
4
目前这是最好的答案。该主题中其他所有内容都是虚假信息(!) 感谢您清理所有那些糟糕的旧测试。 - Ben
6
"InnerHTML不会破坏您的事件处理程序或搞乱任何jQuery引用,但它会使存储在jQuery.cache中的数据变为孤立状态,从而导致内存泄漏,因为管理该数据的唯一引用是您刚刚销毁的元素。" - user1106925
10
jsperf测试在这里出现了问题。测试引擎报告如下:“错误:在迭代过程中未重新创建DOM节点,测试结果将毫无意义。” - senderle
3
我正在制作一个DOM课程,由于看起来jsperf.com已经失踪并且可能要一段时间,我在jsben.ch上创建了一个页面。截至今天,“innerHTML”仍然是赢家。我已经花了太长时间在这个兔子洞里,但在2020年,我不得不想知道为什么我们还没有一个.clear()或者.empty()的DOM方法… - Scott Means
显示剩余11条评论

48
如果您使用jQuery:
$('#foo').empty();

如果你不:

var foo = document.getElementById('foo');
while (foo.firstChild) foo.removeChild(foo.firstChild);

47
var myNode = document.getElementById("foo");
var fc = myNode.firstChild;

while( fc ) {
    myNode.removeChild( fc );
    fc = myNode.firstChild;
}
如果可能存在受jQuery影响的后代元素,那么您必须使用某种方法来清理jQuery数据。
$('#foo').empty();

jQuery的.empty()方法将确保清除与被移除元素相关联的任何数据。

如果您仅使用DOM方法删除子元素,则该数据将保留。


innerHTML方法是迄今为止最高效的。话虽如此,由jquery的.empty()清理的数据:使用递归循环消除了与子标记相关联的jquery内部数据(速度较慢,但可以显著减少内存使用),如果您附加了没有使用jquery的事件,例如使用.onclick=...。如果在要删除的子元素中没有这样的事件,则设置innerHTML不会泄漏。因此,最好的答案是-这取决于情况。 - Chris Moschini
2
为什么不直接将firstChild表达式放在while循环的条件中呢?while ( myNode.firstChild ) { - Victor Zamanian
while(fc = myNode.firstChild){ myNode.removeChild(fc); } - Valen

27

使用elm.replaceChildren()

它是试验性的,支持并不广泛,但在没有参数的情况下执行将完成您要求的操作,并且比循环遍历每个子元素并删除它更有效。如前所述,用空字符串替换innerHTML会在浏览器端进行HTML解析。

MDN文档

更新现在已得到广泛支持


最新版本的Chrome和Safari刚刚得到了它。再过几个月就好了。天啊,我喜欢自动更新。 - Marc M.
一个空字符串会进行多少HTML解析? - NateS

25

最快的...

var removeChilds = function (node) {
    var last;
    while (last = node.lastChild) node.removeChild(last);
};

感谢Andrey Lushnikov提供的他在jsperf.com上的链接(很酷的网站!)。
编辑:需要明确的是,在Chrome浏览器中,firstChild和lastChild之间没有性能差异。顶部答案提供了一个良好的性能解决方案。

不带LINT警告的相同代码: clear: function (container) { for (;;) { var child = container.lastChild; if (!child) break; container.removeChild(child); } } - cskwg

10

如果你只想要节点本身而不包含它的子元素,你也可以像这样复制它:

var dupNode = document.getElementById("foo").cloneNode(false);

取决于您试图实现什么目标。


3
也许你甚至可以跟着这个语句使用parent.replaceChild(cloned, original)?这样做可能比一个个删除子节点更快,并且适用于支持DOM的所有文档类型(包括SVG在内的XML文档)。使用innerHTML设置可能比一个个删除子节点更快,但是这只适用于HTML文档。应该有时间测试一下。 - Maarten
14
使用 addEventListener 添加的事件监听器不会被复制。 - Matthew
如果您可以接受这个限制,那么它肯定是非常快的:http://jsperf.com/innerhtml-vs-removechild/137 - DanMan

9

ECMAScript 6使之易于理解且简洁

myNode.querySelectorAll('*').forEach( n => n.remove() );

这回答了这个问题,并且移除了“所有子节点”

如果myNode中有文本节点,它们无法使用CSS选择器进行选择,在这种情况下,我们还需要应用(也):

myNode.textContent = '';

实际上,最后一个可能是最快和更有效/高效的解决方案。

.textContent.innerText.innerHTML 更高效,参见:MDN


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接