为什么要y.innerHTML = x.innerHTML; 被避免?

假设我们在页面上有一个DIV x ,我们想复制(复制粘贴)该DIV的内容到另一个DIV y 。 我们可以这样做:

 y.innerHTML = x.innerHTML; 

或与jQuery:

 $(y).html( $(x).html() ); 

但是,看来这种方法不是一个好主意,应该避免。

(1)为什么要避免这种方法?

(2)应该怎么做呢?


更新:
为了这个问题,我们假设在DIV x中没有带ID的元素。
(对不起,我忘了在我原来的问题中涵盖这个案例。)

结论:
我已经在下面发表了我自己的回答(正如我原本的意图)。 现在,我也计划接受我自己的答案:P ,但是孤独的回答太神奇了,我不得不接受它。

这种将HTML元素从一个地方“复制”到另一个地方的方法是错误理解浏览器的结果。 浏览器不会将HTML文档保存在内存中,并根据JavaScript的命令反复修改HTML。

当浏览器第一次加载页面时,它parsing HTML文档并将其转换为DOM结构。 这是遵循W3C标准的对象关系(主要是…)。 原来的HTML从此完全是多余的。 浏览器不关心原始HTML结构是什么; 它对网页的理解是从它创build的DOM结构。 如果您的HTML标记是不正确/无效的,那么Web浏览器会以某种方式更正这个标记; DOM结构将不会以任何方式包含无效的代码。

基本上,HTML应该被视为一种序列化DOM结构的方式,通过互联网传递或存储在本地文件。

因此,它不应该用于修改现有的网页。 DOM(文档对象模型)具有用于改变页面内容的系统。 这是基于节点的关系,而不是基于HTML序列化。 所以当你添加一个liul ,你有这两个选项(假设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); 

现在,第一个选项看起来简单得多,但这只是因为浏览器已经为您抽象了很多:在内部,浏览器必须将元素的子元素转换为string,然后附加一些内容,然后将string转换回一个DOM结构。 第二个选项对应于浏览器对于正在发生的事情的原生理解。

第二个主要的考虑是想想HTML的局限性。 当你想到一个网页时,并不是所有与元素相关的东西都可以被序列化为HTML。 例如,用x.onclick = function();绑定的事件处理程序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文档可能有助于理解这种做事方式:

  • appendChild
  • cloneNode
  • childNodes

现在这个问题(与上面的代码示例中的选项2一样)是非常详细的,比innerHTML选项要长得多。 这是当你感激有一个JavaScript库,为你做这种事情。 例如,在jQuery中:

 $('#y').html($('#x').clone(true, true).contents()); 

这对你想要发生的事情更加明确。 除了具有各种性能优势和保存事件处理程序之外,它还可以帮助您了解代码的function。 这对于JavaScript程序员的灵魂是有好处的,并且使得奇怪的错误显着减less了!

  1. 您可以复制需要唯一的ID。
  2. jQuery的克隆方法调用像$(element).clone(true); 将克隆数据和事件监听器,但ID仍然会被克隆。 所以为了避免重复的ID,不要使用ID来克隆需要的项目。
  1. 应该避免这种情况,因为这样就会丢失 DOM元素上可能存在的任何处理程序。

  2. 你可以尝试通过追加DOM元素的克隆而不是完全覆盖它们。

首先,我们来定义在这里必须完成的任务:

DIV x所有子节点必须被“复制”(连同其所有后代=深层复制)并“粘贴”到DIV中。 如果x的后代有一个或多个绑定的事件处理程序,我们可能希望这些处理程序继续处理这些副本(一旦它们被放置在y )。

现在,这不是一个微不足道的任务。 幸运的是,jQuery库(以及所有其他stream行的库,我也假设)提供了一个方便的方法来完成这个任务: .clone() 。 使用这种方法,解决scheme可以这样写:

 $( x ).contents().clone( true ).appendTo( y ); 

上述解决scheme是对问题(2)的回答。 现在,我们来解决问题(1):

这个

 y.innerHTML = x.innerHTML; 

不只是一个坏主意 – 这是一个糟糕的主意。 让我解释…

上述说法可以分解为两个步骤。

  1. expression式x.innerHTML被评估,

  2. 该expression式的返回值(这是一个string)被分配给y.innerHTML

我们要复制的节点( x的子节点)是DOM节点。 它们是存在于浏览器内存中的对象。 在评估x.innerHTML ,浏览器这些DOM节点序列化 (串化)为一个string(HTML源代码string)。

现在,如果我们需要这样一个string(例如将其存储在数据库中),那么这个序列化将是可以理解的。 但是,我们不需要这样的string(至less不是最终产品)。

在第2步中,我们将这个string分配给y.innerHTML 。 浏览器通过parsing导致一组DOM节点的string来评估这个DOM节点,然后将其插入DIV y (作为子节点)。

所以,总结一下:

x – > string化 – > HTML源代码string – > parsing – >节点(副本)

那么,这种方法有什么问题呢? 那么,DOM节点可能包含的属性和function不能,因此也不会被序列化 。 最重要的这种function是绑定到x后代的事件处理程序 – 这些元素的副本不会绑定任何事件处理程序。 处理程序在这个过程中迷路了。

这里可以做一个有趣的比喻:

数字信号 – > D / A转换 – >模拟信号 – > A / D转换 – >数字信号

正如您可能知道的那样,由此产生的数字信号并不是原始数字信号的精确副本 – 有些信息在这个过程中丢失了。

我希望你现在明白为什么应该避免使用y.innerHTML = x.innerHTML

我不会这样做,只是因为你要求浏览器重新parsing已经被parsing的HTML标记。

我更倾向于使用本地cloneNode(true)来复制现有的DOM元素。

 var node, i=0; while( node = x.childNodes[ i++ ] ) { y.appendChild( node.cloneNode( true ) ); } 

那么这真的取决于。 有可能创build具有相同ID的重复元素,这从来不是一件好事。

jQuery也有方法可以为你做到这一点。