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

Pol*_*878 782 javascript dom

我将如何在JavaScript中删除DOM节点的所有子元素?

说我有以下(丑陋的)HTML:

<p id="foo">
    <span>hello</span>
    <div>world</div>
</p>
Run Code Online (Sandbox Code Playgroud)

我抓住了我想要的节点:

var myNode = document.getElementById("foo");
Run Code Online (Sandbox Code Playgroud)

我该怎样才能删除foo<p id="foo"></p>离开的孩子?

我可以这样做:

myNode.childNodes = new Array();
Run Code Online (Sandbox Code Playgroud)

或者我应该使用某种组合removeElement

我希望答案是直接的DOM; 如果您还提供jQuery中的答案以及仅限DOM的答案,那么可以提供额外的分数.

Gab*_*ams 1533

选项1(慢得多,见下面的评论):

doFoo.onclick = () => {
  const myNode = document.getElementById("foo");
  myNode.innerHTML = '';
}
Run Code Online (Sandbox Code Playgroud)

选项2(快得多):

<div id='foo' style="height: 100px; width: 100px; border: 1px solid black;">
  <span>Hello</span>
</div>
<button id='doFoo'>Remove Via innerHTML</button>
Run Code Online (Sandbox Code Playgroud)

  • 顺便说一句,使用lastChild似乎更有效http://jsperf.com/innerhtml-vs-removechild/15 (97认同)
  • 永远不要使用innerHTML =''.别.问题是项目看起来像是被删除但内部,节点持续存在并减慢了速度.出于某种原因,您必须删除所有单个节点.在Google Chrome和IE中都进行了测试.请考虑删除innerHTML"解决方案",因为它是错误的. (26认同)
  • innerHTML仅在您处理HTML时才有效.如果只有SVG内部元素删除将起作用 (23认同)
  • @vsync我不相信Kenji的评论是有根据的.做`innerHTML =''`比较慢,但我不认为这是"错误的".任何内存泄漏的声明都应该有证据支持. (8认同)
  • 从jQuery可以考虑这两个问题:此方法不仅删除子(和其他后代)元素,还删除匹配元素集中的任何文本.这是因为,根据DOM规范,元素中的任何文本字符串都被视为该元素的子节点.并且"为了避免内存泄漏,jQuery在删除元素本身之前从子元素中删除了其他构造,如数据和事件处理程序." (6认同)
  • 你的例子没有错,但我认为在[这个答案](http://stackoverflow.com/a/22966637/1802924)中有一个非常好的例子,jsperf测试是有缺陷的,并没有衡量它的假设至.(链接答案中的声明是他们只删除空div,因为他们错误地认为DOM将在每次测试之间重新填充.)换句话说,innerHTML更快,并且有一个更快的方法(用空替换节点)那些).既然你的回答是如此高度投票,那么如果你还原那部分会更好. (4认同)
  • @ m93a:多次运行你的小提琴给我在6ms到11ms之间的结果.这基本上什么也没告诉我.对于相同测试,结果比每个testrun之间的ms的方差更接近.记录:这不是我的脚本,它的jsperf.com.看看这个是为了更好地理解测试结果:http://stackoverflow.com/a/5262617/859877和http://stackoverflow.com/questions/4986245/how-does-jsperf-work/4996963#4996963 (3认同)
  • 使用.remove()的@micha http://jsperf.com/innerhtml-vs-removechild/151更快. (3认同)
  • @Andrey Lushnikov:取决于哪个浏览器和哪个版本.实际上,firstChild总体上是最好的:http://jsperf.com/innerhtml-vs-removechild/250 (3认同)
  • @Kenji,当然浏览器足够智能以防止内存泄漏.您是否设法在浏览器中导致内存泄漏? (3认同)
  • @chharvey您正在修改当您以这种方式使用forEach时正在迭代的集合. (3认同)
  • @micha我认为你的测试不对.我在jsfidle(http://jsfiddle.net/6K6mv/5/)上写了我的一个,这是结果.时间:14毫秒; INNERHTML:7ms; TEXTCONTENT:7ms (2认同)
  • @Abacus务必小心,并确保目标受众的浏览器可以使用它。http://perfectionkills.com/whats-wrong-with-extending-the-dom/ (2认同)
  • 这是荒唐的。为什么性能在这里很重要?是 120 fps 还是一次性操作? (2认同)
  • 如果我想删除 10 个子元素,并且每个子元素都附加了一个事件,我应该先删除事件,然后删除元素本身,还是不需要删除事件,我可以只删除元素本身? (普通 JavaScript) (2认同)

npj*_*hns 101

目前接受的答案是关于innerHTML较慢(在IE和Chrome中至少)的错误,正如m93a正确提到的那样.

使用此方法可以大大加快Chrome和FF的速度(这会破坏附加的jquery数据):

var cNode = node.cloneNode(false);
node.parentNode.replaceChild(cNode, node);
Run Code Online (Sandbox Code Playgroud)

在FF和Chrome的遥远的第二,在IE中最快:

node.innerHTML = '';
Run Code Online (Sandbox Code Playgroud)

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

最快的DOM操作方法(仍比前两个慢)是Range删除,但IE9之前不支持范围.

var range = document.createRange();
range.selectNodeContents(node);
range.deleteContents();
Run Code Online (Sandbox Code Playgroud)

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

$(node).empty();
Run Code Online (Sandbox Code Playgroud)

证据在这里:

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节点,测试结果将毫无意义." (10认同)
  • *"InnerHTML不会破坏你的事件处理程序或弄乱任何jquery引用"*它将孤立于`jQuery.cache`中的数据,导致内存泄漏,因为唯一可用于管理该数据的引用是在你刚刚销毁的元素上. (6认同)
  • 这是目前最好的答案.此线程中的其他所有内容都是虚假信息(!)感谢您清理所有那些糟糕的旧测试. (4认同)
  • 你的jsperf完全崩溃了.那不是很好的证据. (2认同)
  • 我正在学习 DOM 课程,由于 jsperf.com 看起来像是 mia,并且可能会持续一段时间,因此我在 [jsben.ch](https://jsben.ch/DwfAd) 上创建了一个页面。截至今天,“innerHTML”仍然是赢家。我已经在这个兔子洞上花了太长时间了,但到了 2020 年,我不得不想知道为什么我们还没有 `.clear()` 或 `.empty()` DOM 方法...... (2认同)

Mas*_*eed 67

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

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

container.replaceChildren(...arrayOfNewChildren);
Run Code Online (Sandbox Code Playgroud)

这将同时做到:

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

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

container.replaceChildren();
Run Code Online (Sandbox Code Playgroud)

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

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

Gib*_*olt 50

使用现代的Javascript,用remove!

const parent = document.getElementById("foo");
while (parent.firstChild) {
    parent.firstChild.remove();
}
Run Code Online (Sandbox Code Playgroud)

这是在ES5中编写节点删除的更新方法.它是vanilla JS,读起来比以前的版本好多了.

大多数用户应该使用现代浏览器,或者您可以根据需要进行调整.

浏览器支持 - 2018年4月91%

  • 由于parent.children/parent.childNodes是实时列表,for循环将不起作用; 调用remove会修改列表,因此不能保证迭代器迭代所有内容.例如,在chrome中,您最终会跳过其他所有节点.最好使用`while(parent.firstChild){parent.removeChild(parent.firstChild); 循环.https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/children https://developer.mozilla.org/en-US/docs/Web/API/Node/childNodes (9认同)
  • 很酷的速记,虽然不太可读:`while(parent.firstChild &&!parent.firstChild.remove());` (3认同)
  • 这在 Typescript 中不起作用...而是使用 `while (selectElem.firstElementChild) { selectElem.firstElementChild.remove(); }` (3认同)
  • 当性能不是什么大问题时的单线:`[...parent.childNodes].forEach(el =&gt; el.remove());` (2认同)

use*_*716 43

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

while( fc ) {
    myNode.removeChild( fc );
    fc = myNode.firstChild;
}
Run Code Online (Sandbox Code Playgroud)

如果你有可能有jQuery受影响的后代,那么你必须使用一些方法来清理jQuery数据.

$('#foo').empty();
Run Code Online (Sandbox Code Playgroud)

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

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

  • innerHTML不是更高效!http://jsperf.com/innerhtml-vs-removechild (22认同)
  • 为什么不直接将 firstChild 表达式放在 while 循环的条件中呢?`同时(myNode.firstChild){` (2认同)

Ple*_*and 35

如果你使用jQuery:

$('#foo').empty();
Run Code Online (Sandbox Code Playgroud)

如果你不这样做:

var foo = document.getElementById('foo');
while (foo.firstChild) foo.removeChild(foo.firstChild);
Run Code Online (Sandbox Code Playgroud)


Gab*_*mer 20

最快的...

var removeChilds = function (node) {
    var last;
    while (last = node.lastChild) node.removeChild(last);
};
Run Code Online (Sandbox Code Playgroud)

感谢Andrey Lushnikov与jsperf.com(酷网站!)的链接.

编辑:要清楚,在firstChild和lastChild之间Chrome没有性能差异.最佳答案显示了良好的性能解决方案.


Mar*_* M. 11

使用elm.replaceChildren().

它是实验性的,没有广泛的支持,但是在没有参数的情况下执行时会满足您的要求,并且比循环遍历每个孩子并删除它更有效。如前所述,用innerHTML空字符串替换将需要浏览器部分的 HTML 解析。

MDN 文档

  • 这对于最近的浏览器支持还没有用。但考虑到几年后,这绝对是可行的方法。感谢您指出。 (2认同)

Dan*_*Man 9

如果您只想让节点没有子节点,您也可以像这样复制它:

var dupNode = document.getElementById("foo").cloneNode(false);
Run Code Online (Sandbox Code Playgroud)

取决于你想要实现的目标.

  • 这不会复制使用`addEventListener`添加的事件侦听器. (12认同)
  • 也许你甚至可以用`parent.replaceChild(克隆,原创)来跟随它?这可能比逐个删除子项更快,并且应该支持DOM的所有内容(所以每种类型的XML文档,包括SVG).设置innerHTML也可能比逐个删除子项更快,但这对除HTML文档之外的任何内容都不起作用.应该测试一段时间. (3认同)

Mar*_*tra 9

Ecma6 使其变得简单而紧凑

\n
myNode.querySelectorAll(\'*\').forEach( n => n.remove() );\n
Run Code Online (Sandbox Code Playgroud)\n

这回答了问题,并删除 \xe2\x80\x9call 子节点\xe2\x80\x9d

\n

如果存在属于myNode它们的文本节点,则可以使用 CSS 选择器选择\xe2\x80\x99,在这种情况下,我们\xe2\x80\x99ve 也可以应用:

\n
myNode.textContent = \'\';\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

.textContent.innerText比和更高效.innerHTML,请参阅:MDN

\n


Shi*_*vCK 7

一个单行代码,用于迭代地node从 DOM 中删除 a 的所有子级

Array.from(node.children).forEach(c => c.remove())
Run Code Online (Sandbox Code Playgroud)

或者

[...node.children].forEach(c => c.remove())
Run Code Online (Sandbox Code Playgroud)


Max*_*xim 7

我审查了所有问题并决定测试最关键测试用例的性能:

\n
    \n
  • 我们需要清除内容
  • \n
  • 我们需要保留原始元素
  • \n
  • 我们需要删除很多子元素
  • \n
\n

所以我的 HTML:

\n
<div id="target">\n  <div><p>Title</p></div>\n  <!-- 1000 at all -->\n  <div><p>Title</p></div>\n</div>\n
Run Code Online (Sandbox Code Playgroud)\n
// innerHTML\ntarget.innerHTML = "";\n
Run Code Online (Sandbox Code Playgroud)\n
// lastChild\nwhile (target.hasChildNodes()) {\n    target.removeChild(target.lastChild);\n}\n
Run Code Online (Sandbox Code Playgroud)\n
// firstChild\nwhile (target.hasChildNodes()) {\n    target.removeChild(target.firstChild);\n}\n
Run Code Online (Sandbox Code Playgroud)\n
// replaceChildren\ntarget.replaceChildren();\n
Run Code Online (Sandbox Code Playgroud)\n
结果:
\n
Checked test: innerHTML x 1,222,273 ops/sec \xc2\xb10.47% (63 runs sampled)\nChecked test: lastChild x 1,336,734 ops/sec \xc2\xb10.87% (65 runs sampled)\nChecked test: firstChild x 1,313,521 ops/sec \xc2\xb10.74% (64 runs sampled)\nChecked test: replaceChildren x 743,076 ops/sec \xc2\xb11.08% (53 runs sampled)\n\nChecked test: innerHTML x 1,202,727 ops/sec \xc2\xb10.83% (63 runs sampled)\nChecked test: lastChild x 1,360,350 ops/sec \xc2\xb10.72% (65 runs sampled)\nChecked test: firstChild x 1,348,498 ops/sec \xc2\xb10.78% (63 runs sampled)\nChecked test: replaceChildren x 743,076 ops/sec \xc2\xb10.86% (53 runs sampled)\n\nChecked test: innerHTML x 1,191,838 ops/sec \xc2\xb10.73% (62 runs sampled)\nChecked test: lastChild x 1,352,657 ops/sec \xc2\xb11.42% (63 runs sampled)\nChecked test: firstChild x 1,327,664 ops/sec \xc2\xb11.27% (65 runs sampled)\nChecked test: replaceChildren x 754,166 ops/sec \xc2\xb11.88% (61 runs sampled)\n
Run Code Online (Sandbox Code Playgroud)\n
结论
\n

现代 API 最慢。第一个孩子和最后一个孩子的方式是相等的,如果比较多个情况,可能会出现一些副作用。但你可以并排看到:

\n
Checked test: firstChild x 1,423,497 ops/sec \xc2\xb10.53% (63 runs sampled)\nChecked test: lastChild x 1,422,560 ops/sec \xc2\xb10.36% (66 runs sampled)\n\nChecked test: firstChild x 1,368,175 ops/sec \xc2\xb10.57% (65 runs sampled)\nChecked test: lastChild x 1,381,759 ops/sec \xc2\xb10.39% (66 runs sampled)\n\nChecked test: firstChild x 1,372,109 ops/sec \xc2\xb10.37% (66 runs sampled)\nChecked test: lastChild x 1,355,811 ops/sec \xc2\xb10.35% (65 runs sampled)\n
Run Code Online (Sandbox Code Playgroud)\n
Checked test: lastChild x 1,364,830 ops/sec \xc2\xb10.65% (64 runs sampled)\nChecked test: firstChild x 1,365,617 ops/sec \xc2\xb10.41% (65 runs sampled)\n\nChecked test: lastChild x 1,389,458 ops/sec \xc2\xb10.50% (63 runs sampled)\nChecked test: firstChild x 1,387,861 ops/sec \xc2\xb10.40% (64 runs sampled)\n\nChecked test: lastChild x 1,388,208 ops/sec \xc2\xb10.43% (65 runs sampled)\nChecked test: firstChild x 1,413,741 ops/sec \xc2\xb10.47% (65 runs sampled)\n
Run Code Online (Sandbox Code Playgroud)\n

PS浏览器:火狐111.0.1

\n


bjb*_*568 6

element.textContent = '';
Run Code Online (Sandbox Code Playgroud)

它就像innerText,标准除外.这是慢一点removeChild(),但它更容易使用,不会有太大的性能差异的,如果你没有太多的东西可删除.


jer*_*oen 5

回应 DanMan、Maarten 和 Matt。在我的结果中,克隆节点来设置文本确实是一种可行的方法。

// @param {node} node
// @return {node} empty node
function removeAllChildrenFromNode (node) {
  var shell;
  // do not copy the contents
  shell = node.cloneNode(false);

  if (node.parentNode) {
    node.parentNode.replaceChild(shell, node);
  }

  return shell;
}

// use as such
var myNode = document.getElementById('foo');
myNode = removeAllChildrenFromNode( myNode );
Run Code Online (Sandbox Code Playgroud)

这也适用于不在 dom 中的节点,当尝试访问父节点时返回 null。此外,如果您需要安全,在添加内容之前节点为空,这非常有帮助。考虑下面的用例。

// @param {node} node
// @param {string|html} content
// @return {node} node with content only
function refreshContent (node, content) {
  var shell;
  // do not copy the contents
  shell = node.cloneNode(false);

  // use innerHTML or you preffered method
  // depending on what you need
  shell.innerHTML( content );

  if (node.parentNode) {
    node.parentNode.replaceChild(shell, node);
  }

  return shell;
}

// use as such
var myNode = document.getElementById('foo');
myNode = refreshContent( myNode );
Run Code Online (Sandbox Code Playgroud)

我发现这个方法在替换元素内的字符串时非常有用,如果您不确定节点将包含什么,而不是担心如何清理混乱,而是重新开始。

  • 有史以来最糟糕的事情。如果用克隆节点替换原始节点,则会丢失对原始节点的引用。所有事件处理程序和其他绑定都不起作用。 (3认同)

Nat*_*n K 5

这是另一种方法:

function removeAllChildren(theParent){

    // Create the Range object
    var rangeObj = new Range();

    // Select all of theParent's children
    rangeObj.selectNodeContents(theParent);

    // Delete everything that is selected
    rangeObj.deleteContents();
}
Run Code Online (Sandbox Code Playgroud)


Ado*_*Ren 5

这是我通常做的:

HTMLElement.prototype.empty = function() {
    while (this.firstChild) {
        this.removeChild(this.firstChild);
    }
}
Run Code Online (Sandbox Code Playgroud)

瞧,稍后您可以使用以下命令清空任何 dom 元素:

anyDom.empty()
Run Code Online (Sandbox Code Playgroud)


emu*_*emu 5

使用范围循环对我来说感觉最自然:

for (var child of node.childNodes) {
    child.remove();
}
Run Code Online (Sandbox Code Playgroud)

根据我在 Chrome 和 Firefox 中的测量,速度大约慢 1.3 倍。在正常情况下,这也许并不重要。