老话题:
一般来说 innerHTML 只处理 html 即可,很方便代替冗余的dom操作,但是也有场合需要 从服务端返回一段代码+html一起付给innerHTML,
这种情况下script是并不执行的,有兴趣可以试试
<div id="scriptTest">
</div>
<script type="text/javascript">
window.onload=function(){
document.getElementById("scriptTest").innerHTML="bbbb<script>alert(1);<"+"/"+"script>";
};
</script>
其原因就是:script解析与运行只在页面第一次加载过程中产生,页面加载完毕后,是不会执行script了。
而extjs 也考虑到了这个问题,比如:Ext.Element.update 不是简单的调用innerhtml ,它有 loadScripts 的参数配置,可以使得赋值给innerhtml的同时运行脚本。
Ext.Element.update (附注释):
update : function(html, loadScripts, callback){
if(typeof html == "undefined"){
html = "";
}
//如果指明不包含脚本直接执行哦
if(loadScripts !== true){
this.dom.innerHTML = html;
if(typeof callback == "function"){
callback();
}
return this;
}
var id = Ext.id();
var dom = this.dom;
html += '<span id="' + id + '"></span>';
//定时判断id span是否已经解析完毕,span 解析完了,那么html也解析完了,span在他前面么
//防止 html 脚本中引用到 html 中的元素
E.onAvailable(id, function(){
var hd = document.getElementsByTagName("head")[0];
//脚本识别
var re = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig;
//外部脚本识别
var srcRe = /\ssrc=([\'\"])(.*?)\1/i;
var typeRe = /\stype=([\'\"])(.*?)\1/i;
var match;
//html中包含有脚本,提炼出来单独执行
while(match = re.exec(html)){
var attrs = match[1];
var srcMatch = attrs ? attrs.match(srcRe) : false;
//外部脚本,在head中添加dom标签,动态载入脚本
if(srcMatch && srcMatch[2]){
var s = document.createElement("script");
s.src = srcMatch[2];
var typeMatch = attrs.match(typeRe);
if(typeMatch && typeMatch[2]){
s.type = typeMatch[2];
}
hd.appendChild(s);
// 内部脚本直接运行
}else if(match[2] && match[2].length > 0){
if(window.execScript) {
window.execScript(match[2]);
} else {
window.eval(match[2]);
}
}
}
//删除检测html节点载入完毕指示元素
var el = document.getElementById(id);
if(el){Ext.removeNode(el);}
if(typeof callback == "function"){
callback();
}
});
//只赋值html即可,脚本在html解析完毕后单独运行
dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, "");
return this;
},
分析:
代码要点:
1.利用正则表达式抽出脚本单独执行。
2.防止脚本中引用插入html的元素,利用轮训等待html解析完毕后再执行脚本。(在html最后加一个指示元素)
3.IE 使用 window.execScript 使得脚本在脱离当前闭包,在全局域内运行,firefox则使用 window.eval 来脱离当前闭包,直接使用 eval 会使得在当前闭包运行。
缺点:
Ext update 不能保证脚本执行顺序问题,不像浏览器解析外部脚本有严格顺序(block特性),动态载入 互相依赖脚本 的话顺序得不到保证,解决的话,得利用队列,并根据ie的 readystatechange与firefox的onload判断脚本载入完毕 ,手动保证脚本载入执行顺序,很麻烦。
改进:
由于 ext 中定时器要多次轮训判断制定 id 是否存在,其中的正则表达式其实可以缓存:
var RE_TAG = /<(\w+)/,
RE_SIMPLE_TAG = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
RE_SCRIPT = /<script([^>]*)>([\s\S]*?)<\/script>/ig,
RE_SCRIPT_SRC = /\ssrc=(['"])(.*?)\1/i,
RE_SCRIPT_CHARSET = /\scharset=(['"])(.*?)\1/i;
function setHTML(elem, html, loadScripts, callback) {
if (!loadScripts) {
setHTMLSimple(elem, html);
S.isFunction(callback) && callback();
return;
}
var id = S.guid('ks-tmp-');
html += '<span id="' + id + '"></span>';
//see S.globalEval(text);
//if text contains html() then will reset public shared RE_SCRIPT
//so dupliacate our own
var RE_SCRIPT_INNER = new RegExp(RE_SCRIPT);
// 确保脚本执行时,相关联的 DOM 元素已经准备好
S.available(id, function() {
var hd = S.get('head'),
match, attrs, srcMatch, charsetMatch,
t, s, text;
//share between intervals
RE_SCRIPT_INNER.lastIndex = 0;
while ((match = RE_SCRIPT_INNER.exec(html))) {
attrs = match[1];
srcMatch = attrs ? attrs.match(RE_SCRIPT_SRC) : false;
// script via src
if (srcMatch && srcMatch[2]) {
s = doc.createElement('script');
s.src = srcMatch[2];
// set charset
if ((charsetMatch = attrs.match(RE_SCRIPT_CHARSET)) && charsetMatch[2]) {
s.charset = charsetMatch[2];
}
s.async = true; // make sure async in gecko
hd.appendChild(s);
}
// inline script
else if ((text = match[2]) && text.length > 0) {
S.globalEval(text);
}
}
// 删除探测节点
(t = doc.getElementById(id)) && DOM.remove(t);
// 回调
S.isFunction(callback) && callback();
});
setHTMLSimple(elem, html);
}
不过需要注意的是:要复制RE_SCRIPT为RE_SCRIPT_INNER,在定时器间共享即可(每次设置 lastIndex ),不可全局共享,防止在动态执行的脚本中再调用html导致全局的RE_SCRIPT被重置!
PS: appendChild(elment) 与脚本
firefox 下当 elment 是通过 innerHTML 而包含 script 节点时,其内的脚本会执行:
// firefox 会执行
div = doc.createElement('DIV');
div.innerHTML = 'html 3<script>alert("set innerHTML via appendChild");<\/script>';
t.appendChild(div);
附录:
注意点:
1. innerHTML对于大部分 dom node 是可读可写的,但是也有例外 COL, COLGROUP, FRAMESET, HEAD, HTML, STYLE, TABLE, TBODY, TFOOT, THEAD, TITLE, TR 的innerhtml为只读。所以不能table.innerHTML= "<tr>....</tr>" 来插入行了,需要利用 div.innerHTML="<table><tr>...</tr></table>";得到 div.childNodes[0].childNodes 并插入到 table 中,以及对应 tr,thead 都有对应的嵌套标签包装,详见:KISSY.DOM.create
2.当使用 innerhtml 插入 html时,希望里面包含的 inline script 执行时,可以设置标签的defer 。(外联没用)。
3.当 html 只包含标签时,innerhtml不起作用:<script>为 NoScope元素,所以最好带个 scoped element ,例如文本节点或者input:
injectionDiv.innerHTML='<input type="hidden"/><script defer>alert("hello");</' + 'script>';
mozilla 的script机制 :一句话 defer 不保证,没用。
Have Your DOM and Script It Too
另外一种使得 html 与 script 打包运行的方法,非常巧妙。通过在原 html 后加一个 img 标签,在其 onload 内写代码即可,也省去了 extjs 例子中的 available 判断:
<p>This is the results of our Ajax call.</p>
<img src="../images/loaded.gif" alt=""
onload="alert('Now that I have your attention...');this.parentNode.removeChild(this);" />
本文探讨了innerHTML属性在处理HTML和脚本时的行为差异,特别是在动态插入脚本时的注意事项。介绍了Ext.Element.update方法如何处理innerHTML中的脚本,并提供了一种改进方案。
827

被折叠的 条评论
为什么被折叠?



