JavaScript 文件加载优化策略:Async、Defer 与动态脚本详解

Felix
Felix
发布时间:2026-10-13 | 4人浏览过
原创

JavaScript 脚本的加载方式直接影响页面的渲染速度和用户体验。本文详细解析了 defer、async 以及动态脚本加载三种核心优化策略,分析其在处理 DOM 阻塞、执行顺序及浏览器兼容性方面的差异,帮助开发者根据具体场景选择最合适的加载方案以提升 Web 性能。

JS 引擎与渲染引擎的交互机制

在浏览器中,当渲染引擎(Rendering Engine)解析到 <script> 标签时,会暂停文档的解析并将控制权移交给 JavaScript 引擎。如果该标签引用的是外部资源,浏览器必须等待文件下载并执行完毕后,才会继续解析后续的 HTML 内容。这种阻塞行为会导致页面出现白屏或延迟渲染。

基础优化:脚本位置调整

最常见的优化手段是将 <script> 标签放置在 <body> 元素的末尾。这样做可以让渲染引擎优先构建 DOM 树并绘制页面,尽早脱离白屏状态,从而提前触发 DOMContentLoaded 事件。

注意事项:尽管将脚本置于底部是良好实践,但在 iOS Safari、Android Browser 以及 iOS WebView 等环境中,即使脚本位于尾部,仍可能存在类似的阻塞现象或时序问题。因此,需要结合更明确的属性或动态加载策略来进行深度优化。

现代方案:Defer 属性

defer 是 HTML4 中定义的脚本属性,用于指示浏览器在下载外部脚本的同时不阻塞 HTML 解析。

  • 工作流:当渲染引擎遇到带有 defer 的脚本时,会后台并行下载该资源,同时继续解析 HTML。直到整个 HTML 文档解析完毕,脚本才按照它们在文档中出现的顺序依次执行。
  • 执行顺序:严格遵循文档中的声明顺序。例如:
<script src="outside1.js" defer></script>
<script src="outside2.js" defer></script>

上述代码确保 outside1.js 在 outside2.js 之前执行。

兼容性与 IE 下的 Hack

defer 在现代浏览器中得到广泛支持,但对于 IE9 以下的旧版浏览器,其实现可能存在偏差,导致脚本非顺序执行。
如果在 IE8/IE7 环境下需要强制顺序执行,可以使用以下 Hack:在两个 defer 脚本之间插入一个空的 <script> 标签来重置解析状态。

<script src="outside1.js" defer></script>
<script></script> <!-- Hack -->
<script src="outside2.js" defer></script>

H5 新特性:Async 属性

async 是 HTML5 引入的另一个脚本属性,它提供了另一种加载和执行模式。

  1. 渲染引擎解析到 <script async> 时,开始并行下载脚本,同时继续解析剩余 HTML。
  2. 一旦脚本下载完成,浏览器立即暂停 HTML 解析,将控制权交给 JS 引擎执行脚本。
  3. 脚本执行完毕后,浏览器恢复 HTML 解析。

关键区别:async 是“下载完即执行”,不保证执行顺序。先下载完的先执行。因此,对于存在依赖关系的文件(如 A 依赖 B),不应使用 async,而应使用 defer。

高级技巧:动态脚本加载

除了静态标签,还可以通过 JavaScript 动态创建 <script> 元素来实现异步加载。这是许多模块加载器(如 RequireJS)的基础原理。

基础异步加载函数

function asyncAdd(src){
  var script = document.createElement('script');
  script.src = src;
  document.head.appendChild(script);
}
// 加载 js 文件
asyncAdd("test.js");

这种方式不会阻塞主线程,但缺点是所有脚本并发加载,执行顺序不可控,无法处理文件间的依赖关系。

保持顺序的动态加载

如果需要异步加载但保持执行顺序,可以设置 script.async = false。这告诉浏览器以类似 defer 的方式对待动态脚本,即在 DOMContentLoaded 事件前按插入顺序执行。

var asyncAdd = (function(){
  var head = document.head,
    script;
  return function(src){
    script = document.createElement('script');
    script.src= src;
    script.async=false; // 关键参数:保持顺序
    document.head.appendChild(script);
  }
})();
// 加载文件
asyncAdd("first.js");
asyncAdd("second.js");
// 或者简便一点
["first.js","second.js"].forEach((src)=>{asyncAdd(src);});

性能对比与最佳实践

在选择加载策略时,需考虑浏览器并发能力的差异:

  • 动态脚本加载:在某些老式浏览器中,可能需要等待 CSS 加载完毕后才能发起 JS 请求,无法充分利用并发优势。
  • 静态 Async/Defer:允许 CSS 和 JS 文件完全并发加载,性能通常优于动态创建节点的方式。

取舍建议与 Tips

  1. 位置优先:无论使用何种技术,原则上尽量将脚本置于 <body> 底部,以减少对初始渲染的阻塞。
  2. 降级兼容:如果需要同时利用 async 的非阻塞特性和 defer 的顺序控制,可以同时添加两者:<script src="test.js" async defer>。现代浏览器会根据支持的属性生效(通常 async 优先),旧浏览器则回退到同步加载或 defer 行为。
  3. 依赖管理:大多数业务场景下,脚本之间存在逻辑依赖,因此 defer 通常是更安全、更通用的选择。

以上就是 JavaScript 文件加载优化策略的详细内容,更多关于 JavaScript 性能优化的资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表