本文详解 JavaScript 中 cloneNode 方法的 true 与 false 参数区别,通过代码示例展示子节点与文本节点的克隆行为。重点分析 Internet Explorer 浏览器在使用 attachEvent 绑定事件时,cloneNode 会错误复制事件监听器的 Bug,并提供基于 Dean Edwards addEvent 库的兼容解决方案。
cloneNode 参数详解与示例
当参数为 true 时,执行深度克隆,即完整复制当前节点及其所有后代节点,包括子元素和文本节点,凡存在的内容均会被一并克隆。
当参数为 false 时,执行浅克隆,仅复制当前节点本身,不包含任何子节点,自然也不会复制包裹在其中的文本内容,因为文本内容由独立的文本节点承载。
在某些特定场景下,两种模式的效果是等同的。例如,当目标节点没有任何子节点时,无论使用 true 还是 false,克隆结果一致,比如克隆一个 img 标签。
为了更直观地理解两者的差异,下面通过具体代码示例进行说明。
HTML 结构定义如下:
<div>
<span>Shadow</span> |No Shadow
</div> 首先定义一个变量指向页面中的 span 节点。
var element = document.getElementsByTagName('span')[0];
接着进行克隆操作并比较 innerHTML 的结果:
代码如下:
var t1 = element.cloneNode(false).innerHTML;//不复制子节点
var t2 = element.cloneNode(true).innerHTML;//copy all
alert(t1);
alert(t2); 执行上述代码后,依次弹出的提示框内容分别为空字符串(“”)和“Shadow”。
进一步测试直接克隆文本节点的情况:
var textnode = element.firstChild;//指向文本节点
var t1 = textnode.cloneNode(false).nodeValue;
var t2 = textnode.cloneNode(true).nodeValue;
alert(t1);
alert(t2); 此时,两次 alert 均会输出“Shadow”,说明文本节点无论是否深度克隆,其 nodeValue 属性值均被保留。
【cloneNode的bug】
IE 浏览器 cloneNode 事件克隆问题
在多级联动等场景中,常使用 cloneNode 复制容器节点,但在 Internet Explorer 浏览器中存在一个已知问题:
若使用 attachEvent 为 DOM 元素绑定事件,执行 cloneNode 后,绑定的事件也会被一并复制到新节点上。
相比之下,使用 addEventListener 添加的事件则不会发生此现象,该差异可在 IE 和 Firefox 中进行对比测试:
<!DOCTYPE html> <html> <body> <div id="t">div</div> </body> </html> 在 IE 和 Firefox 中点击第一个 div 均会触发 alert,关键在于第二个 div,在 Firefox 中不会触发事件,而在 IE 中会触发。
目前尚不确定这是否属于严格意义上的 Bug,也有可能是 attachEvent 的设计初衷如此。
由于该问题,项目第一版中未采用 cloneNode 方法。
在寻找解决方案前,进一步扩展测试,考察直接添加 onclick 属性是否会产生同样的克隆行为。
首先测试在 HTML 元素内部直接添加 onclick 属性的情况:
<!DOCTYPE html> <html> <body> <div id="t" onclick="alert(1)">div</div> </body> </html> 测试结果显示,在 IE 和 Firefox 中,事件均会被复制。
再测试通过 JavaScript 动态添加 onclick 属性的情况:
<!DOCTYPE html> <html> <body> <div id="t">div</div> </body> </html> 结果显示,在 IE 和 Firefox 中,事件均不会被复制。这表明只有 attachEvent 会导致此克隆问题。
针对上述问题的解决方法如下:
采用 John Resig 在《精通JavaScript》中推荐的、由 Dean Edwards 编写的 addEvent 和 removeEvent 方法来添加或移除事件。
该方法的优势显而易见,且能有效解决 IE 中 cloneNode 导致的事件复制问题。
其原理是在 IE 中利用 onclick 属性来绑定事件,而前文测试已证实,通过 onclick 绑定的事件不会被 cloneNode 复制。