危险:IE 下使用 cloneNode 注意事项
2012-04-20 by Dron
在开发「百度地裂特效」时,发现了一些匪夷所思的 Bug,第一反应是特效本身的代码与页面上原有的脚本发生了冲突,经过调试发现,问题出现在 cloneNode
。
cloneNode
是 HtmlElement
上的方法,用于创建指定 DOM 节点的拷贝,它接受一个布尔参数 include_all,如果 include_all 设置为 true,则副本会带有指定节点的所有子节点。
问题
然而,<script>
标签也是 DOM 节点,cloneNode
对其有效,经实测各浏览器(尤其是 IE)对 cloneNode
执行结果表现不一致,主要现象为以下两种:
- IE,至少是 IE8 及以下,对某节点
cloneNode
时,如果该节点包含<script>
节点,那么<script>
节点的脚本内容「有可能」会被再次执行一次。 - 非 IE 浏览器,
cloneNode
某节点,包含的<script>
节点的脚本内容不会被再次执行。
IE 以外的浏览器表现令我满意,而针对于上面所述 IE 的「有可能」,还分以下两种情况:
- 如果
cloneNode
一个<script>
节点,无论该节点是外链脚本,还是内嵌脚本,均不会被再次执行。 - 如果
cloneNode
一个其它节点,该节点下包含的内嵌脚本不会被执行,而包含的外链脚本,会被再次执行一次。
这里有一个 demo 复现了 IE 下 cloneNode 的这个问题。
解决方法
解决方法很简单,不用管是什么浏览器,cloneNode
之前,把目标节点下所有的 <script>
标签移除即可,因为脚本已经执行过,移除它的标签并不会造成影响,如下:
function cloneNode(dom){
var scripts = dom.getElementsByTagName("script");
for(var i = scripts - 1, s; i >= 0; i --){
s = scripts[i];
s.parentNode.removeChild(s);
}
return dom.cloneNode(true);
}
因此,我们在使用 cloneNode(true)
时一定要注意思考:所复制节点内的所有子节点是否都是需要的?尽量把不需要的剔除,避免造成负作用,再举个例子,如果复制 <div>
中包含 <iframe>
,而 <iframe>
的页面里有脚本 parent.xxx...
,那么 iframe 里的这些脚本必然会再重新执行一次,<iframe>
页本身没问题,但由于它操作了 parent,那么这个 parent
造成的影响就难以估量了。解决方法是 cloneNode
之后,把副本里包含的 <iframe>
剔除,当然,如果剧情所需,<iframe>
不能剔除的话,就在 <iframe>
页里的脚本自行做判断了。
另外,cloneNode
目标节点内包含 <link>
标签的话,这个估计也会有些影响,我没有做实验,如果没用的话,也是剔除了之。