JavaScript 加载最佳实践:从并行下载到 HTML5 async

作者:安全边界

时间:2026-10-26

浏览:496

本文探讨 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 的加载方式已发生革命性变化。

javascript-download

在深入探讨前,需明确一个核心问题:为何要将 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.write Script 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

二、方案

具体采用哪种方案完全取决于实际需求。下图描述了在不同场景下应使用的方法:

0405-load-scripts-decision-tree-04

总体而言,Script DOM Element 是较为理想的方案。NCZ 在其博客中指出,目前最好的技术组合如下:

  1. 创建两个 JavaScript 文件。第一个文件仅包含用于动态下载 JavaScript 的代码,第二个文件则包含页面所需的其他所有脚本。
  2. 像 <script> 在页面底部(</body> 之前)引入第一个文件。
  3. 创建第二个 <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:

  1. Loading Scripts Without Blocking
  2. The best way to load external JavaScript
  3. Evolution of Script Loading
  4. What is a non-blocking script?
  5. HTML5 – Scripting: async Attribute

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章