日期:2014-05-16  浏览次数:20407 次

BX9013: 动态引入的外部 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',fu