从DOM中删除后,为什么在IE中元素会变为空?

4
以下HTML和JavaScript代码取自此jsFiddle:http://jsfiddle.net/stephenjwatkins/2j3ZB/3/ HTML:
<p class="source">
    Source
</p>
<div id="target">
    <p class="dummy">
        Target
    </p>
</div>
<button id="transfer-button">Transfer</button>

JavaScript:

var sourceEl = $('.source');
var targetEl = $('#target');

$('#transfer-button').click(function() {
    targetEl.html('<p class="dummy">Transferring...</p>');
    setTimeout(function() {
        // Source element will be empty on second attempt to append
        targetEl.html('').append(sourceEl);
    }, 750);
    return false;
});​

请注意,setTimeout和虚拟文本仅用于视觉指示。
可以看到,在源元素被添加并从DOM中删除一次后,IE(所有版本)将在任何进一步的添加时向DOM中添加一个空元素;而其他所有浏览器都会添加正确的非空元素。
另一个增加混淆的方面是,sourceEl仍然具有元素信息(例如,sourceEl.attr('class')将返回“source”)。
我知道缓解问题的方法(例如sourceEl.clone()),但最好能更好地了解为什么IE的行为不同以避免未来相关问题。
是什么导致源元素在替换元素后在IE中变为空?

在我看来,这似乎是一个垃圾回收器的问题,而IE的垃圾回收器似乎比其他浏览器更有效。让我们看看我能否详细说明一下... - Fabrício Matté
2个回答

7

首先,让我们强调一下重要部分:

  1. (第一次点击)将source元素放入target元素中;
  2. (第二次点击)清空target元素并向其附加一个新的子元素(p.dummy),从DOM中有效地删除source
  3. 清空target元素并尝试重新附加source,但是它已不再存在于DOM中。

乍一看,这在任何浏览器中都不会起作用,因为source元素已经从DOM中删除了。这里的“魔法”是JavaScript的垃圾收集器。浏览器看到sourceEl仍然在范围内(在setTimeout闭包中),不会销毁sourceEl jQuery对象中引用的DOM元素。

问题在于JScript(Microsoft的Javascript实现)的垃圾收集器,而不是JScript在设置元素的innerHTML时处理DOM解析的方式。

其他浏览器只是分离所有childNode(当没有更多活动引用时将被GC收集),并将传递的HTML字符串解析为DOM元素,将它们附加到DOM中。另一方面,Jscript还会删除分离的childNodeinnerHTML/childNode。请参阅此fiddle进行说明。

该元素实际上仍然存在于IE中,并附加到DOM中:

enter image description here

只是它不再有childNode了。

为了防止这种类型的行为,在调用其父级的.html()之前,克隆元素(如问题中所述)或将其分离,如果您打算重新使用该元素而不是“覆盖”它。

这是一个使用.detach()覆盖元素之前的fiddle,在所有浏览器中都能正常工作。


不得不起床解决这个问题,因为我意识到问题不在于 JScript 的 GC,而是它的 innerHTML。如果有人有 ECMAScript 或 JScript 实现规范要补充,请随意添加。 - Fabrício Matté
非常彻底的调查来回答这个问题。我最初也认为这是垃圾收集问题;但是,考虑到其他元素属性仍然存在,它是一个与innerHTML有关的IE怪癖是有道理的。 - Stephen Watkins
1
@StephenWatkins 是的,我在发表这个观点之前也进行了更多的测试 - 我存储了元素的.contents()(其中包括textNode),以确保这不是IE的GC问题 - textNode仍然被缓存,但随着innerHTML而被“清空”。 - Fabrício Matté

2

在我看来,IE表现得很正确,而其他浏览器之所以能够正常工作,是因为它们有神奇的功能。这一切都源于您调用以下代码行:

targetEl.html('<p class="dummy">Transferring...</p>');

这将从页面中删除sourceEl元素。因此,它不再存在于页面上。我猜其他浏览器正在记忆DOM对象,因为仍然有一个变量引用它。但IE将其识别为不再存在于页面上,因此失去了对它的引用。

如您所述,我建议在单击时克隆该对象。这将在JavaScript中创建一个新对象。幸运的是,覆盖同一变量就可以运行。

    sourceEl = sourceEl.clone();

点击此处查看。

编辑:在插入新对象之前,您还可以删除任何可能存在的原始源对象。这样可以解决对于频繁点击的用户而言可能会出现的问题。

setTimeout(function() {
    $('.source').remove();
    targetEl.html('').append(sourceEl);
}, 750);

这句话可以澄清一下:在第二次调用时,targetEl.html('<p class="dummy">Transferring...</p>');。当然,也有可能只是我之前有点脑抽。 - JayC
实际上,即使是我的澄清建议也是错误的...因为首次调用不会对任何具有类“ .source”的项目执行任何操作,因为您尚未接触任何此类项目。 在计时器过去之后的任何调用都将删除sourceEl,因为它实际上在targetEl的innerHTML中(就像Fabrico说的那样)。 - JayC
@JayC,没错,是第二次调用。这是当source元素位于被innerHTML覆盖的元素内部时发生的情况。 - Fabrício Matté
刚刚进行了编辑,以修复那些过于着急的人的问题。虽然不是最理想的解决方案,但至少对于这种情况起作用。 - Richard Rout
-1 我感谢您的回答。然而,正如@FabrícioMatté在他的回答中指出的那样,这实际上并不是IE通过垃圾回收元素引用来正确处理的(我最初也是这么认为的)。相反,这是IE在处理innerHTML时的“怪癖”。请参阅他的答案以获取更多详细信息。 - Stephen Watkins
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接