标准参考
关于 document.write 方法请参考标准文档:DOM Level 2 HTML 规范 1.5. Objects related to HTML documents。
问题描述
当脚本中存在以 document.write 的方式向页面内写入内容时,各浏览器对于执行各个线程的顺序可能不一致。
造成的影响
此现象造成在各浏览器里,某些需要延后执行的代码被立即执行,导致程序出错。
受影响的浏览器
IE6 IE7 IE8 |
---|
问题分析
由于 W3C DOM 标准文档中,没有明确说明使用 document.write 方法外引 JS 文件后,被引用脚本文件内继续重复使用 document.write 引用其他外部脚本文件时的执行顺序问题,因而导致实际环境中各个浏览器具体实现不一。
我们来分析以下代码 A:
<script> document.write("before write h1.js"+"<br/>"); document.write("<script src='h" + "1.js'><\/script>"); document.write("after write h1.js" +"<br/>"); </script>
/* h1.js */ document.write("This is h1.js"+"<br/>");
即在一个 SCRIPT 标签内,通过 document.write 的方式向文档输入内容,并在其间引入 JS 文件 A,A 内也包含 document.write,意在向文档输出某些内容。
各浏览器表现如下:
IE6 IE7 IE8 | Firefox Chrome Safari Opera | |
---|---|---|
执行输出顺序 | before write h1.js after write h1.js This is h1.js | before write h1.js This is h1.js after write h1.js |
即 IE 是在当前 SCRIPT 标记内所有的 document.write 向文档中输出内容完成后,再处理以 document.write 方式引入的 JS 文件内的 document.write 写入流。
对于其他浏览器,则根据代码执行顺序依次处理 document.write 方式写入的内容。
此外,使用 document.write 方式写入可引用的外部 JavaScript 内容后,非 IE Opera 浏览器并不会立即更新 DOM 树。
分析以下代码 B:
<script> document.write('<script src="a.js"><\/script>'); document.write('<DIV id="d1"></DIV>'); alert(document.getElementById('d1')===null); </script>
/* a.js */ //可以不包含代码
示例脚本通过 document.write 的方式向文档输入其他的 SCRIPT 标记引入其他 JS 文件以及其他内容。立即写入一个 DIV 标记后通过 getElementById 方法得到该元素的 DOM 引用。
各浏览器表现如下:
IE6 IE7 IE8 Opera | Firefox Chrome Safari |
---|---|
false | true |
对于以上测试代码,IE Opera 表现为在执行到语句 "document.write('<DIV id="d1"></DIV>');" 后立即更新了文档的 DOM 结构(向文档内插入了 ID 为 d1 的 DIV 标记)。
而在其他浏览器里,在当前 SCRIPT 标记的执行流运行完成后,才更新文档的 DOM 结构。
此外,还存在另外问题,分析以下代码 C :
<script> document.write('<script id="d1" src="a.js"><\/script>'); document.write('<script id="d2"></scirpt>'); alert([document.getElementById('d1'),document.getElementById('d2')); </script>
/* a.js */ //可以不包含代码
各浏览器表现如下:
IE6 IE7 IE8 Opera | Firefox Chrome Safari |
---|---|
[Element, Element] | [Element, null] |
本例中,Firefox Safari Chrome 浏览器下,id="d2" 的 Script 元素将输出 null。 除非 d1 不是加载外部资源,d2 元素才可能会立即被获取到;或者同上例一般,在当前脚本块执行流完后才可以访问到 d2、这个问题原因不明,Firefox Safari Chrome 这些浏览器可能因为脚本元素 d1 的外部资源加载而导致 d2 元素没有被及时放入 DOM Tree。
解决方案
如果外部引用的 JS 文件内程序,要求在执行顺序上一致,请避免使用 document.write 语句引入的 JS 程序文件中再次使用他来加载外部 JS 文件。
--------------------------------------------
标准参考
无。
问题描述
页面开发过程中,为了避免页面加载时引入过多外部 JS 文件,导致阻塞页面内容下载及渲染的情况出现。将会采用页面内容加载完成后,动态加载外部 JavaScript 文件的方法来解决此类问题。但是,需要注意的是,常用动态插入外部脚本文件的方法在各浏览器中的执行顺序并不一致。
造成的影响
对于动态插入的 SCRIPT 文件,不能保证在各浏览器能阻塞其后脚本的执行。
受影响的浏览器
所有浏览器 |
---|
问题分析
使用 appenChild insertBefore 等方法向文档中动态插入 SCRIPT 节点后,各浏览器中对脚本的执行顺序存在差异。
以下例子中均使用脚本代码插入远程文件:http://code.jquery.com /jquery-1.4.2.js ( jQuery 源码 ),该文件中定义了全局变量 $,当远程脚本文件加载完成后该变量将可用 。
情况1:
<script> var js = document.createElement("script"); document.getElementsByTagName("head")[0].appendChild(js); js.src = 'http://code.jquery.com/jquery-1.4.2.js'; </script> <script type="text/javascript"> alert($) </script>
代码首先创建 SCRIPT 标记并插入到 HEAD 标记中,再将 SCRIPT 的 src 属性指向外部 JS 文件,由之后的 SCRIPT 标记中代码调用外部程序全局变量。
各浏览器表现及分析如下:
IE Firefox Opera | 弹出($)函数体。此方法中动态附加进文档的 js 文件会阻断下一个 SCRIPT 标记内的代码解析,直至它全部解析完。 |
---|---|
Chrome Safari | 脚本出错,"$ is not defined"。此方法中动态附加进文档的 js 文件不会阻断下一个 SCRIPT 标记内的代码解析。 |
情况2:
<script type="text/javascript"> var js = document.createElement("script"); js.src = 'http://code.jquery.com/jquery-1.4.2.js'; document.getElementsByTagName("head")[0].appendChild(js); </script> <script type="text/javascript"> alert($) </script>
代码首先创建 SCRIPT 标记,将 src 属性指向外部 JS 文件。最后插入到 HEAD 标记中,由之后的 SCRIPT 标记中代码调用外部程序全局变量。
各浏览器表现及分析如下:
Firefox Opera | 弹出($)函数体。此方法中动态附加进文档的 js 文件会阻断下一个 SCRIPT 标记内的代码解析,直至它全部解析完。 |
---|---|
IE Chrome Safari | 脚本出错,"$ is not defined"。此方法中动态附加进文档的 js 文件不会阻断下一个 SCRIPT 标记内的代码解析。 |
情况3:
<script type="text/javascript"> var js = document.createElement("script"); document.getElementsByTagName("head")[0].appendChild(js); js.src = 'http://code.jquery.com/jquery-1.4.2.js'; alert($) </script>
代码首先创建 SCRIPT 标记,将 src 属性指向外部 JS 文件。最后插入到 HEAD 标记中,其后代码立即调用外部程序全局变量。
各浏览器表现及分析如下:
所有浏览器 | 脚本出错,"$ is not defined"。此方法中动态附加进文档的 js 文件不会阻断同一个 SCRIPT 标记内的代码解析。 |
---|
情况4:
<script id="a"></script> <script type="text/javascript"> var a=document.getElementById('a'); a.src = 'http://code.jquery.com/jquery-1.4.2.js'; alert($) </script>
代码获取某个 SCRIPT 标记的引用,在将其 src 属性指向外部 JS 文件,其后代码立即调用外部程序全局变量。
各浏览器表现及分析如下:
所有浏览器 | 脚本出错,"$ is not defined"。此方法中动态附加进文档的 js 文件不会阻断同一个 SCRIPT 标记内的代码解析。 |
---|
情况5:
<script id="a" ></script> <script type="text/javascript"> var a=document.getElementById('a'); a.src = 'http://code.jquery.com/jquery-1.4.2.js'; </script> <script type="text/javascript"> alert($) </script>
代码获取某个 SCRIPT 标记的引用,在将其 src 属性值变更为外部 JS 文件,由之后的 SCRIPT 标记中代码调用外部程序全局变量。
各浏览器表现及分析如下:
IE Firefox Opera | 弹出($)函数体。此方法中动态附加进文档的 js 文件会阻断下一个 SCRIPT 标记内的代码解析,直至它全部解析完。 |
---|---|
Chrome Safari | 脚本出错,"$ is not defined"。此方法中动态附加进文档的 js 文件不会阻断下一个 SCRIPT 标记内的代码解析。 |
综合以上情况,对于动态插入的 SCRIPT 文件,使用不同的插入方法将有不同的表现,不能保证在各浏览器能阻塞其后脚本的执行。
解决方案
对于必须动态附加到文档的外部 js 文件,要保证动态引入的脚本全部执行完成后,才能执行后续代码。
可以将此部分代码封装后调用,如:
function loadJS(url, success) { var domScript = document.createElement('script'); domScript.src = url; success = success || function(){}; domScript.onload = domScript.onreadystatechange = function() { if (!this.readyState || 'loaded' === this.readyState || 'complete' === this.readyState) { success(); this.onload = this.onreadystatechange = null; this.parentNode.removeChild(this); } } document.getElementsByTagName('head')[0].appendChild(domScript); } //执行加载外部 JS 文件 loadJS('a.js',function (){ loadJS('b.js',function (){ loadJS('c.js',function (){ alert('ok'); }); }); });