假设我们在页面上有一个DIV x
,我们希望将该DIV的内容复制(“复制粘贴”)到另一个DIV y
。我们可以这样做:
y.innerHTML = x.innerHTML;
或使用jQuery:
$(y).html( $(x).html() );
然而,看来这种方法不是一个好主意,应该避免。
(1)为什么要避免这种方法?
(2)应如何做呢?
更新
为了这个问题,让我们假设DIV x
内没有ID的元素。
(对不起,我忘了在原来的问题中报道这个案子。)
结论:
我已经在下面发布了我自己的答案(正如我原先的意图)。现在,我也计划接受我自己的回答:P
,但是我的答案是如此令人惊讶,以至于我不得不接受它。
答案 0 :(得分:75)
这种将HTML元素从一个地方“复制”到另一个地方的方法是误解浏览器所做的事情的结果。浏览器不会在某处将HTML文档保留在内存中,而是根据JavaScript中的命令反复修改HTML。
当浏览器首次加载页面时,解析 HTML文档并将其转换为DOM结构。这是遵循W3C标准的对象关系(好吧,主要是......)。从那时起,原始HTML完全冗余。浏览器不关心原始HTML结构是什么;它对网页的理解是从它创建的DOM结构。如果您的HTML标记不正确/无效,将通过Web浏览器以某种方式进行更正; DOM结构不会以任何方式包含无效代码。
基本上,HTML应该被视为一种串行化DOM结构的方式,这种结构将通过互联网传递或存储在本地文件中。
因此,它不应该用于修改现有的网页。 DOM(文档对象模型)具有用于改变页面内容的系统。这基于节点的关系,而不是HTML序列化。因此,当您向li
添加ul
时,您有两个选项(假设ul
是列表元素):
// option 1: innerHTML
ul.innerHTML += '<li>foobar</li>';
// option 2: DOM manipulation
var li = document.createElement('li');
li.appendChild(document.createTextNode('foobar'));
ul.appendChild(li);
现在,第一个选项看起来简单得多,但这只是因为浏览器为你抽象了很多:在内部,浏览器必须将元素的子元素转换为字符串,然后附加一些内容,然后转换为字符串回到DOM结构。第二个选项对应于浏览器对正在发生的事情的原生理解。
第二个主要考虑因素是考虑HTML的局限性。当您考虑网页时,并非与该元素相关的所有内容都可以序列化为HTML。例如,与x.onclick = function();
或x.addEventListener(...)
绑定的事件处理程序将不会在innerHTML
中复制,因此不会复制它们。因此y
中的新元素将不具有事件侦听器。这可能不是你想要的。
所以解决这个问题的方法是使用原生DOM方法:
for (var i = 0; i < x.childNodes.length; i++) {
y.appendChild(x.childNodes[i].cloneNode(true));
}
阅读MDN文档可能有助于理解这种做法:
现在这个问题(与上面的代码示例中的选项2一样)是非常详细,远远超过innerHTML
选项。这是当你喜欢有一个JavaScript库为你做这种事情。例如,在jQuery中:
$('#y').html($('#x').clone(true, true).contents());
这更明确地说明了你想要发生什么。例如,除了具有各种性能优势和保留事件处理程序之外,它还可以帮助您了解代码的作用。作为一名JavaScript程序员,这对你的灵魂有好处,并且使得奇怪的错误显着降低!
答案 1 :(得分:9)
$(element).clone(true);
将克隆数据和事件侦听器,但ID仍将被克隆。因此,为避免重复ID,请不要对需要克隆的项目使用ID。答案 2 :(得分:7)
应该避免,因为那样你丢失可能已经在那里的任何处理程序 DOM元素。
您可以尝试通过附加DOM元素的克隆来解决这个问题,而不是完全覆盖它们。
答案 3 :(得分:7)
首先,让我们定义必须在此完成的任务:
DIV x
的所有子节点必须“复制”(连同其所有后代=深拷贝)并“粘贴”到DIV y
中。如果x
的任何后代有一个或多个绑定到它的事件处理程序,我们可能希望这些处理程序继续处理副本(一旦它们放在y
内)。
现在,这不是一项微不足道的任务。幸运的是,jQuery库(以及我认为的所有其他流行的库)提供了一种方便的方法来完成这项任务:.clone()
。使用此方法,解决方案可以这样编写:
$( x ).contents().clone( true ).appendTo( y );
以上解决方案是问题(2)的答案。现在,让我们解决问题(1):
此
y.innerHTML = x.innerHTML;
不仅仅是一个坏主意 - 它是一个糟糕的主意。让我解释一下......
以上陈述可分为两个步骤。
评估表达式x.innerHTML
,
该表达式(字符串)的返回值已分配给y.innerHTML
。
我们要复制的节点(x
的子节点)是DOM节点。它们是浏览器内存中存在的对象。评估x.innerHTML
时,浏览器序列化(字符串化)这些DOM节点为字符串(HTML源代码字符串)。
现在,如果我们需要这样的字符串(例如将其存储在数据库中),那么这种序列化将是可以理解的。但是,我们不需要这样的字符串(至少不是最终产品)。
在第2步中,我们将此字符串分配给y.innerHTML
。浏览器通过解析对字符串进行评估,该字符串会生成一组DOM节点,然后将这些节点插入到DIV y
中(作为子节点)。
所以,总结一下:
x
的子节点 - &gt; 字符串化 - &gt; HTML源代码字符串 - &gt; 解析 - &gt;节点(副本)
那么,这种方法有什么问题?那么,DOM节点可能包含不能的属性和功能,因此不会被序列化。最重要的此类功能是绑定到x
后代的事件处理程序 - 这些元素的副本不会绑定任何事件处理程序。处理程序在此过程中迷失了方向。
这里可以做一个有趣的比喻:
数字信号 - &gt; D / A转换 - &gt;模拟信号 - &gt; A / D转换 - &gt;数字信号
您可能知道,由此产生的数字信号并不是原始数字信号的精确副本 - 某些信息在此过程中丢失了。
我希望您现在明白为什么应该避免使用y.innerHTML = x.innerHTML
。
答案 4 :(得分:4)
我不会这么做只是因为你要求浏览器重新解析已经解析过的HTML标记。
我更倾向于使用原生cloneNode(true)
来复制现有的DOM元素。
var node, i=0;
while( node = x.childNodes[ i++ ] ) {
y.appendChild( node.cloneNode( true ) );
}
答案 5 :(得分:2)
嗯,这真的取决于。有可能创建具有相同ID的重复元素,这绝不是一件好事。
jQuery也有可以为你做到这一点的方法。