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

IT技术 javascript jquery html browser
2021-02-02 07:34:30

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

y.innerHTML = x.innerHTML;

或使用 jQuery:

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

但是,这种方法似乎不是一个好主意,应该避免使用。

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

(2) 这应该怎么做?


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

结论:
我已经在下面发布了我自己对这个问题的回答(正如我最初打算的那样)。现在,我也打算接受我自己的答案:P,但是lonesomeday的答案太神奇了,我不得不接受它。

6个回答

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

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

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

因此,它不应用于修改现有网页。DOM(文档对象模型)具有用于更改页面内容的系统。这是基于节点的关系,而不是 HTML 序列化。因此,当您将 an 添加li到 a 时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 程序员的灵魂有好处,并且大大降低了出现奇怪错误的可能性!

多么棒的答案。这应该作为文章发表。
2021-03-16 07:34:30
浏览器比innerHTML 能够更好地处理DOM 操作方法的观点是错误的。这在理论上很好,但在实践中,innerHTML 操作要快得多。显然,当您仔细考虑时,这是有道理的,Web 浏览器已针对解析 HTML 进行了优化。我在这里扮演魔鬼的拥护者 - 我个人更喜欢 appendChild 方法。我同意你的大部分观点,但对浏览器执行 innerHTML 操作比直接 DOM 操作更难的前提提出异议。证据就是不存在。
2021-03-23 07:34:30
附加说明,IE(7 及更低版本)在尝试设置innerHTML某些元素时会抛出错误,最常见的是在表格元素上。请参阅此答案MSDN
2021-04-13 07:34:30
  1. 您可以复制需要唯一的 ID。
  2. jQuery 的clone 方法调用类似于,$(element).clone(true);将克隆数据和事件侦听器,但 ID 仍将被克隆。因此,为避免重复 ID,请不要将 ID 用于需要克隆的项目。
@Oded:你的这些编辑真的很快。stackoverflow.com/posts/7392940/revisions
2021-03-21 07:34:30
如果有重复的 id,clone 会做什么?
2021-03-21 07:34:30
@MaximKrizhanovsky jquery 复制它,所以在克隆之后,我会在克隆之后立即更改 id。当页面中有多个相同的 id 时,getElementById 将只返回第一个。
2021-03-30 07:34:30
  1. 应该避免这种情况,因为那样您将丢失该 DOM 元素上可能存在的任何处理程序。

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

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

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.

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

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

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

所以,总结一下:

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

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

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

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

您可能知道,生成的数字信号并不是原始数字信号的精确副本——一些信息在此过程中丢失了。

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

我不会仅仅因为您要求浏览器重新解析已经解析的 HTML 标记而这样做。

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

var node, i=0;

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