如何使用JavaScript将所有HTML元素子项移动到另一个父级?

116

想象一下:

<div id="old-parent">
    <span>Foo</span>
    <b>Bar</b>
    Hello World
</div>
<div id="new-parent"></div>

没有使用jQuery,可以编写什么JavaScript代码将所有子节点(包括元素和文本节点)从old-parent移动到new-parent

我不关心节点之间的空格,但我希望能够捕获杂散的Hello World,它们也需要按原样迁移。

编辑

明确一下,我想最终得到:

<div id="old-parent"></div>
<div id="new-parent">
    <span>Foo</span>
    <b>Bar</b>
    Hello World
</div>

这是一个拟议的重复问题,其答案将会得出:

<div id="new-parent">
    <div id="old-parent">
        <span>Foo</span>
        <b>Bar</b>
        Hello World
    </div>
</div>

3
可能是["Cut and Paste" - 使用JavaScript在DOM中移动节点]的重复问题(链接为https://dev59.com/l3RC5IYBdhLWcg3wW_pk)。 - Marc B
6
@MarcB,这不是一个重复问题。你链接的问题没有涉及移动所有子节点,也没有任何答案回答我需要移动不同类型的HTML节点(例如文本节点)的需求。 - Drew Noakes
2
答案正是你所需要的。DOM 是一棵树。移动一个节点,该节点的所有子节点也会随之移动。 - Marc B
@MarcB 有时候你不想要目标中的父对象。例如考虑将TODO列表中所有元素从“待办”父元素移动到“已完成”父元素。 - Drew Noakes
5个回答

153
基本上,您想循环遍历旧父节点的每个直接后代,并将其移动到新父节点。任何直接后代的子级将随其一起移动。

var newParent = document.getElementById('new-parent');
var oldParent = document.getElementById('old-parent');

function move() {
  while (oldParent.childNodes.length > 0) {
    newParent.appendChild(oldParent.childNodes[0]);
  }
}
#old-parent {
  background-color: red;
}

#new-parent {
  background-color: green;
}
<div id="old-parent">
  <span>Foo</span>
  <b>Bar</b> Hello World
</div>
<div id="new-parent"></div>
<br>
<button onclick="move()" id="button">Move childs</button>

外部链接


12
为了确保高效运行:while (oldParent.childNodes.length) { newParent.appendChild(oldParent.firstChild); }意思是将 oldParent 的子节点逐一移动到 newParent 中,直到 oldParent 不再有子节点为止。 - Gibolt
4
请注意,childNodes[]通常也包含空文本节点(源代码中每个换行符都有一个)。 - Dercsár
5
@Gibolt 我创建了一个 JSPerf 测试,结果似乎表明使用数组访问比调用 firstChild 更快。两者之间的差异可以说是微不足道的,并且这个差异可能在不同的浏览器中有所不同。不过我认为 firstChild 更易于阅读。 - crush
18
while (oldParent.hasChildNodes()) newParent.appendChild(oldParent.firstChild);while (oldParent.firstChild) newParent.appendChild(oldParent.firstChild); 在Chromium中运行速度是原来的2倍,在Firefox中快6到10倍。你修改后的JSPerf - user
3
如果你追求性能,使用DocumentFragment https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment 并一次性移动所有元素可能会更有益。 - Íhor Mé
显示剩余9条评论

48

现代方式:

newParent.append(...oldParent.childNodes);
  1. .append.appendChild的替代方法。主要区别是它可以一次接受多个节点,甚至是纯字符串,例如.append('hello!')
  2. oldParent.childNodes是可迭代的,所以可以使用...展开为.append()的多个参数。

除IE外,所有浏览器都支持该方法。


看起来非常优雅。我想知道与其他答案相比是否有性能差异。 - Drew Noakes
如果有的话,它必须是最小的。如果有的话,它会更快,因为它只需要一个append调用而不是多个。 - fregante
1
警告:不适用于IE11(永远不会,可能)。 - Rene van der Lende
不错,但我进行了一些简单的测试,在Chrome中while()仍然稍微快一些。如果可能,请自行进行测试,不要只听我的话。 - lepe

9

这是一个简单的函数:

function setParent(el, newParent)
{
    newParent.appendChild(el);
}

elchildNodes 是要移动的元素newParent要将 el 移动到的元素,所以你可以像这样执行函数:

var l = document.getElementById('old-parent').childNodes.length;
var a = document.getElementById('old-parent');
var b = document.getElementById('new-parent');
for (var i = l; i >= 0; i--)
{
    setParent(a.childNodes[0], b);
}

Here is the Demo


1
这将呈现HTML:<div id="new-parent"><div id="old-parent">...</div></div> - crush
1
那也行不通,因为当你移动子节点时,长度会发生变化。所以,你最终只会移动一半的节点。你需要向后迭代,或者像我一样使用 while 循环。 - crush
还是不行。你需要这样做:for (var i = l; i >= 0; i--) 或者 while (document.getElementById('old-parent').length > 0)。此外,你的演示中有错误。 - crush
@crush 我已经用新的 fiddle 编辑了我的答案。此外,你的问题中没有说明不要颠倒元素的顺序。 - Cilan
a[i], b 应该改为 a.childNodes[0], b(使用 0 不翻转元素)。 - crush
显示剩余11条评论

0

这是一个简单的解决方案 - 切换节点 ID 并更改它们的顺序。如果您在 ID 名称中不使用 -,那么您可以这样做。

oldParent.id='xxx';
newParent.id='oldParent';
xxx.id='newParent';
oldParent.parentNode.insertBefore(oldParent,newParent);
#newParent { color: red }
<div id="oldParent">
    <span>Foo</span>
    <b>Bar</b>
    Hello World
</div>
<div id="newParent"></div>


这并没有移动子元素,只是以1999年的方式交换了父元素。如果您向oldParent HTML添加更多属性,这将变得明显。 - fregante

-2
只有在你不需要做除了将内部代码(innerHTML)从一个地方转移到另一个地方之外的任何操作时,这个答案才真正有效。
// Define old parent
var oldParent = document.getElementById('old-parent');

// Define new parent
var newParent = document.getElementById('new-parent');

// Basically takes the inner code of the old, and places it into the new one
newParent.innerHTML = oldParent.innerHTML;

// Delete / Clear the innerHTML / code of the old Parent
oldParent.innerHTML = '';

希望这能帮到你!


1
这个方法可以运行,但我不太想通过HTML字符串进行序列化/反序列化。不过它确实能工作 :) - Drew Noakes
12
它不会复制任何附加到元素的对象和事件。 - Gregory Magarshak
4
这个重新生成DOM,不会移动现有的节点/事件,并且它可能比循环所有子元素要慢得多。 - Jordan
我运行了一些简单的测试,发现这种方法仍然比被接受的答案慢,并且已经有缺点被解释过了。 - lepe

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