让插入到 innerHTML 中的 script 跑起来的代码第12页

2018-10-15 18:04

阅读:610

在做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中的内容的开头附加一段不是脚本的内容,就可以了。但不要试图附加一个空内容的标签,或者空格、回车、换行等,这将不起作用,开头的脚本仍然会被忽略。也不要试图附加,虽然这可以让开头的脚本不再被忽略,但这个仍然会影响原有内容的显示,虽然你可能觉得不明显,但是对于挑剔的用户来说,这可能是无法容忍的。因此,为了让附加的内容既可以起到避免开头脚本被忽略的功能,又不会造成不良影响,我们将附加这么一段内容:

<spanstyle=display:none>hackie</span>
虽然上面这段内容有一定的长度,但是它并不会显示,而且这个插入的标签没有id也没有name,所以也不会跟原来内容中的某些标签的id或者name产生冲突。不过这里有一点要注意,这里也要判断是否是IE,然后再决定加不加这段内容,因为其他某些浏览器可能不支持display:none这个CSS修饰(例如OperaMini),如果加上这段代码会影响最终的显示效果。

下面我们来看看如何取出脚本并执行。

取出脚本很容易,只需要用innerHTML所在对象的getElementsByTagName方法就可以了,这个方法对几乎所有的容器标签都管用。取出脚本以后,我们要一一判断它们是外部脚本还是内部脚本。

先来看外部脚本,如果是外部脚本,我们选择了这样一种方法,即先创建这个外部脚本的一个副本对象,并设置它的defer属性为true(这一点是为了让IE浏览器能正确执行),然后用appendChild方法将这个副本对象插入到head中。这里你可能会问,为什么不是插入到innerHTML所在的对象中呢?插入到innerHTML所在的对象中不是更好吗?如果你试一下就会知道,如果插入到innerHTML所在的对象中,在IE浏览器中没有问题,但是在Mozilla/Firefox和Opera浏览器中会有一些问题。问题是如果在Firefox上这样做,浏览器会停止响应(这是在Firefox1.5上的测试结果,其他版本是否有此问题,尚不得知),而在Opera上,脚本会莫名其妙的执行两次(这是在Opera8.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.setTimeout这个函数,让每个脚本都延时一段后再执行,外部脚本延时时间可以设的较长,以保证其能够完全加载,而内部脚本则可以设置为很短,因为一个脚本执行的时间通常是很短的,这样既可以保证不会改变作用域,又可以基本保证脚本执行顺序不会改变了(这种方法对于保证执行顺序上也不一定会100%有效,如果网络非常繁忙,外部脚本可能在设置的时间内加载不完,但至少比直接用eval的时候好多了)。

——————————–
(1)注:在这里,我们用了限定词“可能”,因为有一种情况,脚本会被执行,在下文中你将会看到这种情况。

12下一页阅读全文


评论


亲,登录后才可以留言!