innerhtmlscript:让插入到 innerHTML 中的 script 跑起来的代码

在做 ajax 编程时我们常常需要将 xmlhttp 获取到页面内容通过 innerHTML 来赋给某个容器(比如 div、span 或者 td 等)但是这里存在个问题就是我们将要赋给 innerHTML 页面内容如果包含有脚本这些脚本不管是外部脚本还是内部脚本可能(1)都不会被执行这个问题在某些时候微不足道甚至可以忽略但有些时候这个问题就非常严重它很可能让我们得不到预期结果因此我们需要解决这个问题
如果你读过 MSDN你会发现并非所有插入到 innerHTML 中脚本都不能执行如果这段脚本 script 标签中包含了 defer 属性IE 会正确执行这些脚本但不幸Moziila/Firefox 和 Opera 可不吃这不管 script 标签有没有设置 defer 属性这些浏览器都不会向 IE 那样去执行插入到 innerHTML 中脚本
但不管脚本是否被执行了点我们可以肯定那就是这些脚本确实被插入到了 innerHTML 中如果不相信你可以 alert 下看看但如果你真 alert 了你也可能会发现有种例外情况存在那就是如果脚本在 innerHTML 内容开头那么 IE 浏览器将会忽略掉这段脚本而 Moziila/Firefox 和 Opera 却不会
好了问题分析差不多了我们来看看如何解决吧
解决思路其实很简单那就是将插入到 innerHTML 中所有脚本取出来然后执行不过我们先要解决上面两个问题
先来看第个问题如何避免在 IE 中重复执行 innerHTML 中带有 defer 属性脚本这个很容易只需要先确定浏览器是否是 IE然后再检测将要执行脚本是否带有 defer 属性即可需要注意在判断 IE 浏览器时我们需要避免被 opera 浏览器识别欺骗点我们在后面代码中将会看到它是如何做
接下来看 IE 忽略 innerHTML 开头脚本问题这个也很容易解决只需要在要插入到 innerHTML 中内容开头附加段不是脚本内容就可以了但不要试图附加个空内容标签或者空格、回车、换行等这将不起作用开头脚本仍然会被忽略也不要试图附加 虽然这可以让开头脚本不再被忽略但这个 仍然会影响原有内容显示虽然你可能觉得不明显但是对于挑剔用户来说这可能是无法容忍因此为了让附加内容既可以起到避免开头脚本被忽略功能又不会造成不良影响我们将附加这么段内容:
<span style="display: none">hack ie</span>
虽然上面这段内容有长度但是它并不会显示而且这个插入标签没有 id 也没有 name所以也不会跟原来内容中某些标签 id 或者 name 产生冲突不过这里有点要注意这里也要判断是否是 IE然后再决定加不加这段内容其他某些浏览器可能不支持 display: none 这个 CSS 修饰(例如 Opera Mini)如果加上这段代码会影响最终显示效果
下面我们来看看如何取出脚本并执行
取出脚本很容易只需要用 innerHTML 所在对象 getElementsByTagName 思路方法就可以了这个思路方法对几乎所有容器标签都管用取出脚本以后我们要判断它们是外部脚本还是内部脚本
先来看外部脚本如果是外部脚本我们选择了这样种思路方法即先创建这个外部脚本个副本对象并设置它 defer 属性为 true(这点是为了让 IE 浏览器能正确执行)然后用 appendChild 思路方法将这个副本对象插入到 head 中这里你可能会问为什么不是插入到 innerHTML 所在对象中呢?插入到 innerHTML 所在对象中不是更好吗?如果你试下就会知道如果插入到 innerHTML 所在对象中在 IE 浏览器中没有问题但是在 Mozilla/Firefox 和 Opera 浏览器中会有些问题问题是如果在 Firefox 上这样做浏览器会停止响应(这是在 Firefox 1.5 上测试结果其他版本是否有此问题尚不得知)而在 Opera 上脚本会莫名其妙执行两次(这是在 Opera 8.5 上测试结果其它版本 Opera 是否由此问题也尚不得知)为了避免这些问题所以我选择了插入到 head 中
再来看内部脚本内部脚本内容我们可以直接用脚本对象 text 属性来获取这里我们使用脚本对象 text 属性而不是 innerHTML 属性在 Opera 浏览器中脚本对象 innerHTML 属性是空只有用 text 属性才能获取到脚本内容执行内部脚本直接用 eval 即可但是脚本可能会被包含在 HTML 注释标签中因此我们需要先将注释标签去掉不然在 IE 中会出错
上面分析看上去很完美了但是实际上还是有问题个是 document.write 和 document.writeln 问题这个问题在 Blueidea 上bound0 给出了个思路就是替换掉默认 document.write 和 document.writeln 思路方法不过他用串替换因此只对内部脚本有效对外部脚本就没办法了因此我想了个更通用办法就是直接把 document.write 和 document.writeln 重新定义这样不管内部脚本还是外部脚本执行就都是我们我们自己定义 document.write 和 document.writeln 了不过也有副作用就是这两个在当前页面中就不能再像原来样使用了不过这两个在页面加载完的后般是不会再用到了因此这里重新定义它们所带来副作用影响很小但是还有个问题是尽管这样我们仍然无法保证 document.write 或 document.writeln 输出内容会显示在最合适位置它只是把内容附加到了我们放置内容容器中
个问题是 eval 引起问题个是 Blueidea 上 hutia 说作用域问题个问题是如果用 eval 执行内部脚本内部脚本会在外部脚本加载完的前就开始执行了要解决这个两个问题可以采用 window.Timeout 这个让每个脚本都延时段后再执行外部脚本延时时间可以设较长以保证其能够完全加载而内部脚本则可以设置为很短个脚本执行时间通常是很短这样既可以保证不会改变作用域又可以基本保证脚本执行顺序不会改变了(这种思路方法对于保证执行顺序上也不定会 100% 有效如果网络非常繁忙外部脚本可能在设置时间内加载不完但至少比直接用 eval 时候好多了)
——————————–
(1) 注:在这里我们用了限定词“可能”种情况脚本会被执行在下文中你将会看到这种情况
Tags:  divinnerhtml jsinnerhtml innerhtml innerhtmlscript

延伸阅读

最新评论

发表评论