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

IT技术 javascript dom
2021-01-11 10:27:12

我将如何在 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;如果您还提供 jQuery 中的答案以及仅 DOM 的答案,则可以加分。

6个回答

选项 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 以防检查速度firstChildlastChild(例如,如果元素列表由 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>

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

  • 这种方法保留所有非Element(即#text节点和<!-- comments -->)父级(但不是它们的后代)的子级 - 这在您的应用程序中可能是可取的(例如一些使用内联 HTML 注释来存储模板指令的模板系统)。
  • 这种方法直到最近几年才被使用,因为 Internet Explorer 只lastElementChild在 IE9 中添加了支持

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猴子补丁:

  • 我们可以向ElementJavaScript 中原型添加一个新的方法属性,以简化对它的调用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>

从 jQuery 可以考虑这两个问题: 此方法不仅删除子(和其他后代)元素,而且删除匹配元素集中的任何文本。这是因为,根据 DOM 规范,元素内的任何文本字符串都被视为该元素的子节点。并且“为了避免内存泄漏,jQuery 在删除元素本身之前从子元素中删除了其他构造,例如数据和事件处理程序。”
2021-03-25 10:27:12
@micha jsperf.com/innerhtml-vs-removechild/151使用 .remove() 甚至更快。
2021-03-28 10:27:12
永远永远永远永远不要使用innerHTML = ''。别。问题是这些项目看起来像是被删除了,但在内部,节点仍然存在并减慢了速度。出于某种原因,您必须删除所有单个节点。在谷歌浏览器和 IE 中测试。请考虑删除innerHTML“解决方案”,因为它是错误的。
2021-04-03 10:27:12
只有当您只处理 HTML 时,innerHTML 才有效。如果有例如 SVG 内部只有元素删除将工作
2021-04-05 10:27:12
@vsync 我不相信 Kenji 的评论是有根据的。做的innerHTML = ''比较慢,但我不认为这是“错误的”。任何关于内存泄漏的声明都应该有证据支持。
2021-04-07 10:27:12

innerHTML正如 m93a 正确提到的那样,当前接受的答案是错误的(至少在 IE 和 Chrome 中)。

使用此方法(这会破坏附加的 jquery 数据),Chrome 和 FF 的速度要快得多:

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

对于 FF 和 Chrome,在遥远的一秒钟内,在 IE 中是最快的:

node.innerHTML = '';

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

最快的 DOM 操作方法(仍然比前两种慢)是 Range 移除,但直到 IE9 才支持 Ranges。

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

提到的其他方法似乎具有可比性,但比innerHTML慢很多,除了异常值jquery(1.1.1和3.1.1),它比其他任何方法都慢得多:

$(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 重启的新 url,因为编辑旧 url 不起作用)

Jsperf 的“per-test-loop”通常被理解为“per-iteration”,只有第一次迭代有节点要删除,所以结果没有意义,在发布时,这个线程中的测试设置不正确。

jsperf 测试在这里被破坏了。测试引擎报告以下内容:“错误:在迭代期间未重新创建 Dom 节点,测试结果将毫无意义。”
2021-03-20 10:27:12
您的 jsperf 已完全损坏。这不是很好的证据。
2021-03-25 10:27:12
这是目前最好的答案。此线程中的其他所有内容都是虚假信息 (!) 感谢您清理所有那些糟糕的旧测试。
2021-03-31 10:27:12
我正在研究 DOM 课程,因为它看起来像 jsperf.com 是 mia 并且可能有一段时间,我在jsben.ch上创建了一个页面时至今日,innerHTML依然是赢家。我已经在这个兔子洞上花了太长时间了,但是在 2020 年我不得不想知道为什么我们还没有.clear().empty()DOM 方法......
2021-04-02 10:27:12
“InnerHTML 不会破坏您的事件处理程序或弄乱任何 jquery 引用”它将孤立保存在 中的数据jQuery.cache,从而导致内存泄漏,因为唯一可用于管理该数据的引用位于您刚刚销毁的元素上。
2021-04-09 10:27:12

使用现代 Javascript,带有remove!

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

这是一种在 ES5 中编写节点删除的较新方法。它是 vanilla JS,比依赖父级读起来好得多。

支持所有现代浏览器。

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

很酷的速记,虽然可读性较差: while (parent.firstChild && !parent.firstChild.remove());
2021-03-11 10:27:12
for 循环将不起作用,因为 parent.children / parent.childNodes 是实时列表;调用 remove 会修改列表,因此您的迭代器不能保证迭代所有内容。例如,在 chrome 中,您最终会跳过所有其他节点。最好使用while (parent.firstChild) { parent.removeChild(parent.firstChild); }循环。developer.mozilla.org/en-US/docs/Web/API/ParentNode/children developer.mozilla.org/en-US/docs/Web/API/Node/childNodes
2021-03-18 10:27:12
这在 Typescript 中不起作用......而是使用 while (selectElem.firstElementChild) { selectElem.firstElementChild.remove(); }
2021-03-24 10:27:12
当性能不是什么大问题时的单线: [...parent.childNodes].forEach(el => el.remove());
2021-04-06 10:27:12

2020 年更新 - 使用replaceChildren()API!

现在可以使用(跨浏览器支持)replaceChildrenAPI替换所有子项

container.replaceChildren(...arrayOfNewChildren);

这将同时做到:

  • 删除所有现有的孩子,和
  • 在一次操作中附加所有给定的新孩子。

您还可以使用相同的 API 来删除现有的子项,而不替换它们:

container.replaceChildren();

Chrome/Edge 86+、Firefox 78+ 和 Safari 14+ 完全支持此功能。这是完全指定的行为。这可能比这里提出的任何其他方法都快,因为删除旧孩子和添加新孩子不需要innerHTML,并且一步而不是多个。

这比这里提到的其他方法要慢得多,至少对我来说是这样。
2021-03-12 10:27:12
您介意链接到您的测试,以及您使用的浏览器/平台等详细信息吗?这会让我大吃一惊,至少在 Chrome 上是这样。
2021-03-12 10:27:12
MDN 数据现已更正,并显示完全跨浏览器支持:caniuse.com/?search=replacechildren
2021-03-19 10:27:12
很遗憾@Polaris878 自 2018 年以来一直没有出现。这应该是新的公认答案。
2021-03-25 10:27:12
var myNode = document.getElementById("foo");
var fc = myNode.firstChild;

while( fc ) {
    myNode.removeChild( fc );
    fc = myNode.firstChild;
}

如果您的后代有可能受到 jQuery 影响,那么您必须使用某种方法来清理 jQuery 数据。

$('#foo').empty();

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

如果您只是使用DOM删除子项的方法,则该数据将保留。

while(fc = myNode.firstChild){ myNode.removeChild(fc); }
2021-03-10 10:27:12
为什么不直接将 firstChild 表达式放在 while 循环的条件中呢? while ( myNode.firstChild ) {
2021-03-14 10:27:12
迄今为止,innerHTML 方法的性能最高。也就是说,jquery 的 .empty() 清理的数据:使用递归循环消除 jquery 中与子标签关联的 data()(慢,但可能会显着减少内存使用量),如果您附加事件,则防止内存泄漏jquery,比如使用 .onclick=... 。如果要删除的子级中没有此类事件,则设置innerHTML 不会泄漏。所以最好的答案是 - 这取决于。
2021-03-30 10:27:12