提问人:DigitalZebra 提问时间:10/18/2010 最后编辑:Benjamin LoisonDigitalZebra 更新时间:4/20/2023 访问量:1517624
在 JavaScript 中删除 DOM 节点的所有子元素
Remove all child elements of a DOM node in JavaScript
问:
我将如何删除 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 的答案,则加分。
答:
选项 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
- 如上所述,但使用 .根据 MDN 的说法,这将比浏览器不会调用其 HTML 解析器,而是立即用单个节点替换元素的所有子元素更快。
.textContent
innerHTML
#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
- 循环继续检查,以防万一检查速度比更快(例如,如果元素列表是由 UA 实现为有向链接列表的)。
firstChild
firstChild
lastChild
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
- 这种方法保留了父级(但不是其后代)的所有非(即节点和)子级 - 这在您的应用程序中可能是可取的(例如,一些使用内联 HTML 注释来存储模板指令的模板系统)。
Element
#text
<!-- comments -->
- 这种方法直到最近几年才被使用,因为 Internet Explorer 只在 IE9 中添加了对 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 中为原型添加一个新的 method-property,以简化将其调用为(其中是任何 HTML 元素对象)。
Element
el.clearChildren()
el
- (严格来说,这是一个猴子补丁,而不是一个 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>
评论
innerHTML = ''
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 ( myNode.firstChild ) {
while(fc = myNode.firstChild){ myNode.removeChild(fc); }
如果使用 jQuery:
$('#foo').empty();
如果不这样做:
var foo = document.getElementById('foo');
while (foo.firstChild) foo.removeChild(foo.firstChild);
如果你只想让节点没有它的子节点,你也可以像这样复制它:
var dupNode = document.getElementById("foo").cloneNode(false);
取决于您要实现的目标。
评论
parent.replaceChild(cloned, original)
addEventListener
作为对 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 );
此外,这适用于不在 dom 中的节点,这些节点在尝试访问 parentNode 时返回 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 );
我发现这种方法在替换元素中的字符串时非常有用,如果您不确定节点将包含什么,请不要担心如何清理混乱,而是从头开始。
评论
最快...
var removeChilds = function (node) {
var last;
while (last = node.lastChild) node.removeChild(last);
};
感谢安德烈·卢什尼科夫(Andrey Lushnikov)链接到 jsperf.com(很酷的网站!
编辑:需要明确的是,firstChild和lastChild在Chrome中没有性能差异。最好的答案显示了一个很好的性能解决方案。
评论
innerText是赢家!http://jsperf.com/innerhtml-vs-removechild/133。在之前的所有测试中,父节点的内部 dom 在第一次迭代时被删除,然后在应用于空 div 的 innerHTML 或 removeChild 中删除。
评论
innerText
不过,这是一个专有的 MS 东西。只是说。
box
while
var empty_element = function (element) {
var node = element;
while (element.hasChildNodes()) { // selected elem has children
if (node.hasChildNodes()) { // current node has children
node = node.lastChild; // set current node to child
}
else { // last child found
console.log(node.nodeName);
node = node.parentNode; // set node to parent
node.removeChild(node.lastChild); // remove last node
}
}
}
这将删除元素中的所有节点。
评论
我看到人们在做:
while (el.firstNode) {
el.removeChild(el.firstNode);
}
然后有人说使用更快el.lastNode
但是我认为这是最快的:
var children = el.childNodes;
for (var i=children.length - 1; i>-1; i--) {
el.removeNode(children[i]);
}
你觉得怎么样?
PS: 这个话题对我来说是救命稻草。我的 firefox 插件被拒绝了,因为我使用了 innerHTML。这是很长一段时间的习惯。然后我找到了这个。我实际上注意到了速度差异。加载时,innerhtml 需要一段时间才能更新,但是通过 addElement 它的即时!
评论
for (var i=0...
这是另一种方法:
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();
}
评论
element.textContent = '';
它就像 innerText,除了标准。它比 慢一点,但它更易于使用,如果您没有太多要删除的内容,则不会对性能产生太大影响。removeChild()
评论
正如 m93a 正确提到的那样,目前公认的答案是错误的(至少在 IE 和 Chrome 中)。innerHTML
Chrome 和 FF 使用这种方法的速度要快得多(这将破坏附加的 jquery 数据):
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 操作方法(仍然比前两种慢)是范围删除,但在 IE9 之前不支持范围。
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”,只有第一次迭代有节点要删除,所以结果毫无意义,在发布时,这个线程中的测试设置不正确。
评论
jQuery.cache
innerHTML
.clear()
.empty()
通过 Javascript 删除节点的子节点的最简单方法
var myNode = document.getElementById("foo");
while(myNode.hasChildNodes())
{
myNode.removeChild(myNode.lastChild);
}
使用现代 Javascript,带有 !remove
const parent = document.getElementById("foo")
while (parent.firstChild) {
parent.firstChild.remove()
}
这是在 ES5 中写入节点删除的一种较新的方法。它是香草JS,读起来比依赖父母要好得多。
支持所有现代浏览器。
浏览器支持 - 97% 6月 '21
评论
while (parent.firstChild) { parent.removeChild(parent.firstChild); }
while (parent.firstChild && !parent.firstChild.remove());
[...parent.childNodes].forEach(el => el.remove());
while (selectElem.firstElementChild) { selectElem.firstElementChild.remove(); }
有几种方法可以实现这一点:
最快():
while (node.lastChild) {
node.removeChild(node.lastChild);
}
替代方案(较慢):
while (node.firstChild) {
node.removeChild(node.firstChild);
}
while (node.hasChildNodes()) {
node.removeChild(node.lastChild);
}
通常,JavaScript 使用数组来引用 DOM 节点的列表。因此,如果您有兴趣通过 HTMLElements 数组来执行此操作,这将很好地工作。另外,值得注意的是,因为我使用的是数组引用而不是 JavaScript 原型,所以这应该适用于任何浏览器,包括 IE。
while(nodeArray.length !== 0) {
nodeArray[0].parentNode.removeChild(nodeArray[0]);
}
为什么我们不遵循这里最简单的方法“删除”在里面循环。
const foo = document.querySelector(".foo");
while (foo.firstChild) {
foo.firstChild.remove();
}
- 选择父 div
- 在 While 循环中使用 “remove” 方法消除第一个子元素,直到没有剩下任何子元素。
评论
这是我通常做的:
HTMLElement.prototype.empty = function() {
while (this.firstChild) {
this.removeChild(this.firstChild);
}
}
瞧,稍后您可以使用以下命令清空任何 dom 元素:
anyDom.empty()
评论
domEl.innerHTML = ""
刚刚看到有人在另一个中提到这个问题,并认为我会添加一个我还没有看到的方法:
function clear(el) {
el.parentNode.replaceChild(el.cloneNode(false), el);
}
var myNode = document.getElementById("foo");
clear(myNode);
clear 函数是获取元素并使用父节点将自身替换为没有子节点的副本。如果元素稀疏,则性能提升不大,但当元素有一堆节点时,性能提升就会实现。
评论
使用范围循环对我来说是最自然的:
for (var child of node.childNodes) {
child.remove();
}
根据我在 Chrome 和 Firefox 中的测量,它的速度大约慢了 1.3 倍。在正常情况下,这可能无关紧要。
let el = document.querySelector('#el');
if (el.hasChildNodes()) {
el.childNodes.forEach(child => el.removeChild(child));
}
仅功能方法:
const domChildren = (el) => Array.from(el.childNodes)
const domRemove = (el) => el.parentNode.removeChild(el)
const domEmpty = (el) => domChildren(el).map(domRemove)
domChildren 中的“childNodes”将给出一个直接子元素的 nodeList,当找不到任何元素时,该元素为空。为了映射 nodeList,domChildren 将其转换为数组。domEmpty 将函数 domRemove 映射到删除它的所有元素上。
用法示例:
domEmpty(document.body)
从 body 元素中删除所有子元素。
您可以从容器中删除所有子元素,如下所示:
function emptyDom(selector){
const elem = document.querySelector(selector);
if(elem) elem.innerHTML = "";
}
现在,您可以调用该函数并传递选择器,如下所示:
如果元素有 id = foo
emptyDom('#foo');
如果元素有 class = foo
emptyDom('.foo');
if 元素有标签 = <div>
emptyDom('div')
用。elm.replaceChildren()
它是实验性的,没有广泛的支持,但是在没有参数的情况下执行时,它会做你要求的事情,而且它比遍历每个孩子并删除它更有效。如前所述,替换为空字符串需要浏览器进行 HTML 解析。innerHTML
更新它现在得到广泛支持
评论
element.innerHTML = ""
(或 .textContent)是迄今为止最快的解决方案
这里的大多数答案都是基于有缺陷的测试
例如: https://jsperf.com/innerhtml-vs-removechild/15
此测试不会在每次迭代之间向元素添加新的子元素。第一次迭代将删除元素的内容,然后每个其他迭代将不执行任何操作。
在本例中,速度更快,因为 box.lastChild 在 99% 的时间内为 null
while (box.lastChild) box.removeChild(box.lastChild)
这是一个适当的测试:https://jsperf.com/innerhtml-conspiracy
最后,不要使用 .这会将节点替换为没有其子节点的自身副本。但是,这不会保留事件侦听器,并中断对节点的任何其他引用。node.parentNode.replaceChild(node.cloneNode(false), node)
Ecma6 使它变得简单而紧凑
myNode.querySelectorAll('*').forEach( n => n.remove() );
这将回答问题,并删除“所有子节点”。
如果有属于它们的文本节点不能用CSS选择器来选择,在这种情况下,我们必须应用(也):myNode
myNode.textContent = '';
实际上,最后一个可能是最快,更有效/高效的解决方案。
.textContent
比 和 更有效率,参见:MDN.innerText
.innerHTML
在 2022+ 中,使用 API!replaceChildren()
现在可以使用(支持跨浏览器)replaceChildren
API 替换所有子项:
container.replaceChildren(...arrayOfNewChildren);
这将同时执行以下两项操作:
- 删除所有现有子项,以及
- 在一个操作中追加所有给定的新子项。
您还可以使用相同的 API 仅删除现有子项,而不替换它们:
container.replaceChildren();
Chrome/Edge 86+、Firefox 78+ 和 Safari 14+ 完全支持此功能。这是完全指定的行为。这可能比这里提出的任何其他方法都快,因为删除旧子项和添加新子项不需要 ,并且只需一步而不是多个步骤即可完成。innerHTML
评论
ES6+ 浏览器(2016 年后发布的主要浏览器)的最佳删除方法:
也许有很多方法可以做到这一点,例如 Element.replaceChildren()。 我想向你展示一个有效的解决方案,只有一个重绘和重排支持所有ES6+浏览器。
function removeChildren(cssSelector, parentNode){
var elements = parentNode.querySelectorAll(cssSelector);
let fragment = document.createDocumentFragment();
fragment.textContent=' ';
fragment.firstChild.replaceWith(...elements);
}
用法: :删除所有 className 在removeChildren('.foo',document.body);
foo
<body>
评论
textContent
fragment.replaceChildren(...elements)
fragment.firstChild.replaceWith(...elements)
replaceChildren
replaceWith
replaceChildren
用于迭代从 DOM 中删除 a 的所有子项的单行代码node
Array.from(node.children).forEach(c => c.remove())
或
[...node.children].forEach(c => c.remove())
我查看了所有问题,并决定测试最关键测试用例的性能:
- 我们需要清除内容
- 我们需要保留原始元素
- 我们需要删除很多子元素
所以我的HTML:
<div id="target">
<div><p>Title</p></div>
<!-- 1000 at all -->
<div><p>Title</p></div>
</div>
// innerHTML
target.innerHTML = "";
// lastChild
while (target.hasChildNodes()) {
target.removeChild(target.lastChild);
}
// firstChild
while (target.hasChildNodes()) {
target.removeChild(target.firstChild);
}
// replaceChildren
target.replaceChildren();
结果:
Checked test: innerHTML x 1,222,273 ops/sec ±0.47% (63 runs sampled)
Checked test: lastChild x 1,336,734 ops/sec ±0.87% (65 runs sampled)
Checked test: firstChild x 1,313,521 ops/sec ±0.74% (64 runs sampled)
Checked test: replaceChildren x 743,076 ops/sec ±1.08% (53 runs sampled)
Checked test: innerHTML x 1,202,727 ops/sec ±0.83% (63 runs sampled)
Checked test: lastChild x 1,360,350 ops/sec ±0.72% (65 runs sampled)
Checked test: firstChild x 1,348,498 ops/sec ±0.78% (63 runs sampled)
Checked test: replaceChildren x 743,076 ops/sec ±0.86% (53 runs sampled)
Checked test: innerHTML x 1,191,838 ops/sec ±0.73% (62 runs sampled)
Checked test: lastChild x 1,352,657 ops/sec ±1.42% (63 runs sampled)
Checked test: firstChild x 1,327,664 ops/sec ±1.27% (65 runs sampled)
Checked test: replaceChildren x 754,166 ops/sec ±1.88% (61 runs sampled)
结论
现代 API 最慢。第一个孩子和最后一个孩子的方式是相等的,如果与多个案例进行比较,可能会出现一些副作用。但您可以在这里并排看到:
Checked test: firstChild x 1,423,497 ops/sec ±0.53% (63 runs sampled)
Checked test: lastChild x 1,422,560 ops/sec ±0.36% (66 runs sampled)
Checked test: firstChild x 1,368,175 ops/sec ±0.57% (65 runs sampled)
Checked test: lastChild x 1,381,759 ops/sec ±0.39% (66 runs sampled)
Checked test: firstChild x 1,372,109 ops/sec ±0.37% (66 runs sampled)
Checked test: lastChild x 1,355,811 ops/sec ±0.35% (65 runs sampled)
Checked test: lastChild x 1,364,830 ops/sec ±0.65% (64 runs sampled)
Checked test: firstChild x 1,365,617 ops/sec ±0.41% (65 runs sampled)
Checked test: lastChild x 1,389,458 ops/sec ±0.50% (63 runs sampled)
Checked test: firstChild x 1,387,861 ops/sec ±0.40% (64 runs sampled)
Checked test: lastChild x 1,388,208 ops/sec ±0.43% (65 runs sampled)
Checked test: firstChild x 1,413,741 ops/sec ±0.47% (65 runs sampled)
PS 浏览器:Firefox 111.0.1
评论