JavaScript 加载最佳实践:从并行下载到 HTML5 async
本文探讨 JavaScript 加载的最佳实践,重点解决脚本阻塞页面渲染的问题。通过对比 XHR、DOM 动态创建等六种并行下载方案,推荐 Script DOM Element 为通用最佳方案,并结合 HTML5 的 async 属性实现非阻塞加载,最后以 YUI3 和支付宝实践为例展示动态加载的应用。
熟悉 Web 开发的读者通常对 Yahoo 提出的《加速网站的最佳实践》并不陌生。其中第 35 条建议指出应将脚本置于页面底部(Put Scripts at the Bottom)。依据 HTTP/1.1 规范,同时并行加载多个文件是最理想的状态,而 JavaScript 脚本的加载会阻碍这种并行下载机制。Steve Souders 指出这是 2008 至 2009 年间的观点,如今 JavaScript 的加载方式已发生革命性变化。

在深入探讨前,需明确一个核心问题:为何要将 JS 文件放置在 </body> 标签前的底部?根本原因在于传统上它无法进行平行下载。事实上,并非所有浏览器都不支持 Script 的并行下载,目前主流浏览器大多已支持,仅遗留的老版本浏览器如 IE6/7、Firefox 2/3.0、Safari 3 和 Chrome 1 仍不支持。鉴于 IE6(或基于 IE 内核的浏览器)在中国乃至全球市场仍占较高份额,我们需要一种折衷的解决方案。
一、分析
目前存在六种方法可以实现并行(NON-Blocking)下载:
- XHR Eval – 使用 XHR 下载,并
eval()执行 responseText。 - XHR Injection – 使用 XHR 下载,在页面中动态创建一个 script 元素,并将 responseText 作为其 text。
- Script in Iframe – 将脚本嵌入 HTML 中,使用 iframe 来下载它。
- Script DOM Element – 动态创建一个 script 元素,把
src指向脚本 URL。 - Script Defer – 给 script 标签添加
defer属性。 document.writeScript Tag – 利用document.write把<script src="">添加到 HTML 中。但此方法仅对 IE 有效。
各方案的兼容性情况可查看下图:
| Technique | Parallel Downloads | Differ | Existing Scripts | Busy Indicators | Ensures Order | Size (bytes) |
|---|---|---|---|---|---|---|
| XHR Eval | IE, FF, Saf, Chr, Op | no | no | Saf, Chr | - | ~500 |
| XHR Injection | IE, FF, Saf, Chr, Op | no | yes | Saf, Chr | - | ~500 |
| Script in Iframe | IE, FF, Saf, Chr, Op | no | no | IE, FF, Saf, Chr | - | ~50 |
| Script DOM Element | IE, FF, Saf, Chr, Op | yes | yes | FF, Saf, Chr | FF, Op | ~200 |
| Script Defer | IE, Saf4, Chr2, FF3.1 | yes | yes | IE, FF, Saf, Chr, Op | IE, FF, Saf, Chr, Op | ~50 |
| document.write Script Tag | IE, Saf4, Chr2, Op | yes | yes | IE, FF, Saf, Chr, Op | IE, FF, Saf, Chr, Op | ~100 |
二、方案
具体采用哪种方案完全取决于实际需求。下图描述了在不同场景下应使用的方法:

总体而言,Script DOM Element 是较为理想的方案。NCZ 在其博客中指出,目前最好的技术组合如下:
- 创建两个 JavaScript 文件。第一个文件仅包含用于动态下载 JavaScript 的代码,第二个文件则包含页面所需的其他所有脚本。
- 像
<script>在页面底部(</body> 之前)引入第一个文件。 创建第二个 <script>来执行下载第二个 JavaScript 文件的函数及其他初始化代码。
三、实现代码
基于上述技术,NCZ 推荐第一个文件仅包含实现第二个文件动态加载的代码:
代码如下:
function loadScript(url, callback){
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function(){
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
} 随后,我们可以在页面中这样调用:
代码如下:
<script type="text/javascript" src="http://your.cdn.com/first.js"></script>
<script type="text/javascript">
loadScript("http://your.cdn.com/second.js", function(){
//初始化你的代码
});
</script> 在 HTML5 环境中,我们可以使用 async 属性。该 HTML 属性的作用正是实现我们所需的 NON-Blocking 下载技术。尽管目前支持该属性的浏览器并不多(似乎仅有 Firefox 3.6+ 等),但给需要并行下载的 JavaScript(通常指第一个 JS 文件)添加此属性,也不会影响不支持该属性的浏览器,因此推荐使用。
代码如下:
<script type="text/javascript" async src="foo.js"></script> 四、实践
YUI3 的 Loader 使用了 NCZ 提出的此类方法。在支付宝项目中,我们也采用了类似的方法。此处简要说明。
代码如下:
<script type="text/javascript" charset="utf-8">
// 配置 combo 服务的 PATH
araleConfig = {
combo_host: "http://domain.com",
combo_path: '/path/to/the/compressed/file'
}
</script>
<script type="text/javascript" src="core.js"></script> 在实际使用时,再利用 Loader.use() 来实现代码的动态加载。这不仅仅是动态加载,还包含一定的缓存机制。建议查阅相关的 combo 服务技术文档。目前支付宝前端架构组的工程师在此领域已取得显著进展(根据测试报告,速度表现优异,未来可能会适时开源)。
五、总结
在前端性能优化方面,仍有大量工作可做。随着 HTML5 技术的普及和 JavaScript 技术的不断创新,未来值得期待更多优化手段。前端开发者们加油吧,未来的很多技术主导权应掌握在你们手中。
Reference:
- Loading Scripts Without Blocking
- The best way to load external JavaScript
- Evolution of Script Loading
- What is a non-blocking script?
- HTML5 – Scripting: async Attribute
配置 Matter.js 睡眠机制以优化静止物体性能
2026-11-05 15:43:12
JavaScript console.log高级用法:用table、group和time优化调试输出
2026-11-05 08:04:56
JS变量中存储HTML代码时的特殊字符转义方法
2026-11-04 19:04:09
JS怎么安全转换带千分位或货币符号的字符串为数字
2026-11-04 11:54:14
推荐几款稳定的在线JS反混淆工具,支持Obfuscator.io解密
2026-11-04 08:16:21








