进入正题之前,先考大家一个问题:defer 属性现在有哪些浏览器支持?

喜悦

除了 defer 属性,script 还新增了一个 async 属性,请看 MDC

async Requires Gecko 1.9.2
This Boolean attribute is set to indicate that the browser should, if possible, execute the script asynchronously. If the script cannot be executed asynchronously, or the browser doesn’t support this attribute, the script is executed synchronously (and loading the content blocks until the script finishes running).

理想情况下,利用 async 属性,脚本异步加载将变得非常简单:
async-test.js:

var g = 1;

async-test-1.html:

<script src="async-test.js" async="true"></script>
<script>
    alert(typeof g); // Firefox 3.6 弹出 undefined
</script>

测试页面:async-test-1.html
目前只有 Firefox 3.6 支持 async 属性,希望其它浏览器能迅速跟进。

悲哀

Firefox 3.6 的尝试和探索精神令人钦佩。但是,在引入这个新特性时,也导致了传统异步加载方式的失效:

<script>
    (function(d) {
        var a = d.createElement('script');
        a.src = 'async-test.js';
        d.getElementsByTagName('head')[0].appendChild(a);
    })(document);
</script>
<script>
    alert(typeof g); // 预期结果是 undefined, Firefox 3.6 弹出 number
</script>

测试页面:async-test-2.html
真糟糕!类似问题,还有 Steve Souders 前不久发现的 document.write 在 Firefox 3.6 下的阻塞行为

分析

用 Firebug 可以看到,在 Firefox 3.6 中,script 元素 async 属性的默认值是 false. 换言之,script 元素默认是同步加载的。可以推测,Firefox 3.6 在增加 async 这个属性时,很可能忽略了用 createElement(’script’) 和 document.write(‘<script…’) 等方式创建的 script 元素,async 的属性值应该从默认值 false 调整为 true. 正是这个疏忽,导致了传统加载异步脚本的方式在 Firefox 3.6 中失效。对于 YUI3 和很多站点来说,这实在是太糟糕了。

知道了问题本因,可以得到目前异步加载脚本最安全的方式如下:

<script>
    (function(d) {
        var a = d.createElement('script');
        a.async = true;
        a.src = 'async-test.js';
        d.getElementsByTagName('head')[0].appendChild(a);
    })(document);
</script>

测试页面:async-test-3.html

注意1:所有测试结果,均在无缓存的情况下得出。
注意2:async-test-2.html 在 Opera 10.10 中弹出值非预期,原因未深究,有兴趣者可以进一步挖掘。

感慨

Google 在 ga 的部署脚本 中已经使用上了 async 属性:

<script type="text/javascript">
    (function() {
      var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
      ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
      (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
    })();
  </script>

最后

再考大家一题,在 Firefox 3.6 下,下面的代码,alert 出什么:

<script src="async-test.js" async="false"></script>
<script>
    alert(typeof g); // ?
</script>