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

Šim*_*das 46 html javascript browser jquery

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

y.innerHTML = x.innerHTML;
Run Code Online (Sandbox Code Playgroud)

或者使用jQuery:

$(y).html( $(x).html() );
Run Code Online (Sandbox Code Playgroud)

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

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

(2)应如何做呢?


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

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

lon*_*day 76

这种将HTML元素从一个地方"复制"到另一个地方的方法是误解浏览器所做的事情的结果.浏览器不会在某处将HTML文档保留在内存中,而是根据JavaScript中的命令反复修改HTML.

当浏览器首次加载页面时,它会解析 HTML文档并将其转换为DOM结构.这是遵循W3C标准的对象关系(好吧,主要是......).从那时起,原始HTML完全冗余.浏览器不关心原始HTML结构是什么; 它对网页的理解是从它创建的DOM结构.如果您的HTML标记不正确/无效,将通过Web浏览器以某种方式进行更正; DOM结构不会以任何方式包含无效代码.

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

因此,它不应该用于修改现有的网页.DOM(文档对象模型)具有用于改变页面内容的系统.这基于节点的关系,而不是HTML序列化.所以当你添加一个lia时ul,你有这两个选项(假设ul是list元素):

// 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);
Run Code Online (Sandbox Code Playgroud)

现在,第一个选项看起来更简单了,但这只是因为浏览器为你抽象了很多:在内部,浏览器必须将元素的子元素转换为字符串,然后附加一些内容,然后将字符串转换回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));
}
Run Code Online (Sandbox Code Playgroud)

阅读MDN文档可能有助于理解这种做法:

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

$('#y').html($('#x').clone(true, true).contents());
Run Code Online (Sandbox Code Playgroud)

关于你想要发生什么,这更明确.例如,除了具有各种性能优势和保留事件处理程序之外,它还可以帮助您了解代码的作用.作为一个JavaScript程序员,这对你的灵魂有好处,并且使得奇怪的错误显着降低!

  • 真是个好消息.这应该作为文章发表. (15认同)
  • 浏览器比innerHTML更能处理DOM操作方法的想法是错误的.这在理论上很好,但在实践中,innerHTML操作明显更快.很明显,当您考虑它时,这是有意义的,Web浏览器针对解析HTML进行了优化.我在这里扮演魔鬼的拥护者 - 我个人更喜欢appendChild方法.我同意你的大多数观点,但是问题是浏览器执行innerHTML操作比直接DOM操作更难.证据不存在. (6认同)
  • 另外注意,IE(7和更低版本)在尝试设置某些元素的`innerHTML`时会抛出错误,这些元素通常在表元素上注意到.请参阅[此答案](http://stackoverflow.com/questions/4729644/cant-innerhtml-on-tbody-in-ie)和[MSDN](http://msdn.microsoft.com/en-us/library /ms533897(VS.85).aspx). (2认同)
  • jQuery:对灵魂有益. (2认同)

Joe*_*Joe 9

  1. 您可以复制需要唯一的ID.
  2. jQuery的克隆方法调用就像$(element).clone(true);克隆数据和事件监听器一样,但ID仍然会被克隆.因此,为了避免重复ID,请不要对需要克隆的项使用ID.

  • @Oded:你对这些编辑真的很快.http://stackoverflow.com/posts/7392940/revisions (4认同)
  • F5 .. F5 .. F5 ... F5 ......;) (3认同)

Nea*_*eal 7

  1. 应该避免它,因为那样你就丢失了可能在那个DOM元素上的任何处理程序.

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


Šim*_*das 7

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

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

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

$( x ).contents().clone( true ).appendTo( y );
Run Code Online (Sandbox Code Playgroud)

上述解决方案是问题(2)的答案.现在,我们来解决问题(1):

这个

y.innerHTML = x.innerHTML;
Run Code Online (Sandbox Code Playgroud)

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

上述陈述可分为两个步骤.

  1. 表达式x.innerHTML被评估,

  2. 分配该表达式的返回值(这是一个字符串)y.innerHTML.

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

现在,如果我们需要这样的字符串(例如,将其存储在数据库中),那么这种序列化将是可以理解的.但是,我们不需要这样的字符串(至少不是最终产品).

在第2步中,我们将此字符串分配给y.innerHTML.浏览器通过解析导致一组DOM节点的字符串来对此进行评估,然后将这些节点插入到DIV中y(作为子节点).

所以,总结一下:

子节点x- > 字符串化 - > HTML源代码字符串 - > 解析 - >节点(副本)

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

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

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

您可能知道,由此产生的数字信号并不是原始数字信号的精确副本 - 某些信息在此过程中丢失了.

我希望你现在明白为什么y.innerHTML = x.innerHTML要避免.