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

时间:2011-09-12 19:29:47

标签: javascript jquery html browser

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

y.innerHTML = x.innerHTML;

或使用jQuery:

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

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

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

(2)应如何做呢?


更新
为了这个问题,让我们假设DIV x内没有ID的元素。
(对不起,我忘了在原来的问题中报道这个案子。)

结论:
我已经在下面发布了我自己的答案(正如我原先的意图)。现在,我也计划接受我自己的回答:P,但是我的答案是如此令人惊讶,以至于我不得不接受它。

6 个答案:

答案 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)

  1. 您可以复制需要唯一的ID。
  2. jQuery的clone method调用,如$(element).clone(true);将克隆数据和事件侦听器,但ID仍将被克隆。因此,为避免重复ID,请不要对需要克隆的项目使用ID。

答案 2 :(得分:7)

  1. 应该避免,因为那样你丢失可能已经在那里的任何处理程序 DOM元素。

  2. 您可以尝试通过附加DOM元素的克隆来解决这个问题,而不是完全覆盖它们。

答案 3 :(得分:7)

首先,让我们定义必须在此完成的任务:

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

现在,这不是一项微不足道的任务。幸运的是,jQuery库(以及我认为的所有其他流行的库)提供了一种方便的方法来完成这项任务:.clone()。使用此方法,解决方案可以这样编写:

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

以上解决方案是问题(2)的答案。现在,让我们解决问题(1):

y.innerHTML = x.innerHTML;

不仅仅是一个坏主意 - 它是一个糟糕的主意。让我解释一下......

以上陈述可分为两个步骤。

  1. 评估表达式x.innerHTML

  2. 该表达式(字符串)的返回值已分配给y.innerHTML

  3. 我们要复制的节点(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也有可以为你做到这一点的方法。