IE9是否存在Bug?使用Node.cloneNode(true)时<script>内容执行两次
没错,你碰到的这个情况绝对是IE9特有的问题,完全不符合网页规范——按照标准,文档加载完成后动态插入的<script>(包括通过克隆已有节点得到的脚本)不应该自动执行,而其他现代浏览器(甚至后续的IE10及以上版本)都遵循了这个规则,只有IE9会出现克隆后的脚本重复执行的诡异行为。
问题根源
IE9在处理cloneNode(true)深度克隆包含<script>的节点时,没有正确跟踪脚本的“已执行”状态。页面加载时已经运行过的脚本,在被克隆并重新插入DOM后,IE9会错误地将其识别为全新的未执行脚本,从而再次触发执行逻辑。
复现验证
你提供的测试代码完美复现了这个Bug:
<!DOCTYPE html> <html> <head> <title>IE9 Script Tag Bug Test</title> <script> function ButtonClick(){ var Elem = document.getElementById('mydiv'); var ElemClone = Elem.cloneNode(true); document.body.insertBefore(ElemClone,Elem); } </script> </head> <body> <div id="mydiv"> This is a DIV. <script> alert("hey"); </script> </div> <button onclick="ButtonClick();">Click Me</button> </body> </html>
在IE9模式下打开页面,加载时会弹出"hey"提示;点击按钮后,克隆节点插入DOM时,IE9还会再次弹出这个提示——但在Chrome、Firefox等浏览器里,只会在页面加载时弹出一次。
可行的修复方案
针对这个Bug,有几种实用的解决办法:
克隆前移除脚本元素:在克隆节点之前,先把克隆副本里的所有
<script>元素移除,这样插入DOM后就不会有脚本执行了。示例代码:function ButtonClick(){ var Elem = document.getElementById('mydiv'); // 先克隆原节点,避免修改原DOM结构 var tempClone = Elem.cloneNode(true); // 反向遍历并移除所有script元素(正向遍历会因为节点删除导致索引混乱) var scripts = tempClone.getElementsByTagName('script'); for(var i = scripts.length - 1; i >= 0; i--){ scripts[i].parentNode.removeChild(scripts[i]); } document.body.insertBefore(tempClone,Elem); }修改脚本类型禁用执行:如果需要保留脚本标签但不想让它执行,可以把克隆后的脚本
type属性改成浏览器不识别的类型(比如type="text/template"),这样IE9就不会执行它了。手动构建节点替代克隆:如果不需要完全克隆原节点的所有内容,可以手动创建新DIV,只复制原节点里的非脚本内容,跳过
<script>标签。用innerHTML处理内容:先获取原节点的innerHTML,移除其中的脚本标签后,再创建新节点插入DOM:
function ButtonClick(){ var Elem = document.getElementById('mydiv'); // 用正则移除所有script标签 var cleanHtml = Elem.innerHTML.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); var newDiv = document.createElement('div'); newDiv.innerHTML = cleanHtml; document.body.insertBefore(newDiv,Elem); }
内容的提问来源于stack exchange,提问作者user3163495

