让innerHTML的脚本也能正常执行
问题
有时候,我们想要这么做
var content = [
'<div>xxx</div>',
'<script>alert("aaa");</script>'
].join('');
dom.innerHTML = content;
但是,事与愿违,innerHTML里面的脚本文件通常是不会执行的
先抛出结论
- ie6下,innerHTML中的具有defer属性的内联或者外部脚本都会执行
- ie下,如果第一个节点为script节点,那么会被抛弃,不下载也不执行,就跟不存在一样
- 其他的浏览器均无法执行innerHTML中的脚本
- 所有浏览器中,虽然script不被执行,但除了2的情况外,所有script节点都存在
实现原理
因为结论4,我们可以先把content的内容innerHTML到dom中,这时候所有的script都存在,除了2的情况 为了保证2的情况也可以,我们可以这么做
function fill(dom, content) {
dom.innerHTML = '<i>hack ie content firstchild is script node</i>' + content;
...
}
然后获取所有的script节点
function fill(dom, content) {
dom.innerHTML = '<i>hack ie content firstchild is script node</i>' + content;
var scripts = document.getElementsByTagName('script');
...
}
接下来,只要将所有的script按照类型进行解析
function fill(dom, content) {
dom.innerHTML = '<i>hack ie content firstchild is script node</i>' + content;
var scripts = document.getElementsByTagName('script'),
script;
for (var i = 0, len = scripts.length; i < len; i++) {
script = scripts[0];
if (script.src) {
//外部脚本
loadScript(script.src);
} else {
//内部脚本
evalGlobalScript(script.text || script.textContent || script.innerHTML || "");
}
//去除执行过后的script
if (script.parentNode ) {
script.parentNode.removeChild(script);
}
}
}
这里的evalGlobalScript方法如下,因为eval的代码在严格模式下的作用域跟非严格模式下有区别,因此需要用以下的方法来确保是在global作用域执行
function evalGlobalScript(data) {
if (data && /\S/.test(data)) {
(window.execScript || function (data) {
window["eval"].call(window, data);
})(data);
}
}
最后,请记住结论1, ie6下defer的问题,如果不处理,所有的defer代码会被执行2遍,因此,上面的代码变成
function fill(dom, content) {
dom.innerHTML = '<i>hack ie content firstchild is script node</i>' + content;
var scripts = document.getElementsByTagName('script'),
script;
for (var i = 0, len = scripts.length; i < len; i++) {
script = scripts[0];
//屏蔽结论1
if (ie <= 6 && script.defer) {
continue;
}
if (script.src) {
//外部脚本
loadScript(script.src);
} else {
//内部脚本
evalGlobalScript(script.text || script.textContent || script.innerHTML || "");
}
//去除执行过后的script
if (script.parentNode ) {
script.parentNode.removeChild(script);
}
}
}
需要注意的问题
- 上面只是简单实现,执行顺序无法保障
- 脚本中的
document.write
无法处理,除非解析脚本,或者重载全局document.write
sinaadToolkit.dom.fill