JavaScript 文件加载优化:async、defer 与动态脚本详解

作者:极速慢跑时间:2026-10-29 22:06:03浏览:507

本文深入解析 JavaScript 文件加载对页面性能的影响,详细对比了将脚本置于 body 底部、使用 defer 属性以及使用 async 属性的加载机制与执行顺序。文章还探讨了动态脚本加载的原理及其局限性,并提供了结合 async 与 defer 的向下兼容方案,帮助开发者根据目标浏览器选择合适的加载策略。

JavaScript 加载机制与优化背景

在 JavaScript 引擎的工作机制中,当浏览器渲染引擎解析到 <script> 标签时,会将控制权暂时移交给 JS 引擎。如果该标签引用的是外部资源,浏览器必须等待资源下载完成后才能执行脚本。这一机制可能导致页面渲染阻塞,因此我们需要采取多种优化手段来提升加载效率。

将脚本放置于页面底部

将脚本放置于页面底部

为了让渲染引擎尽早构建并展示 DOM 树,避免页面出现白屏现象,通常建议将 <script> 标签放置在 <body> 标签的末尾。这样做可以尽早触发 DOMContentLoaded 事件。然而,在 iOS Safari、Android 浏览器以及 iOS Webview 等环境中,即使将脚本置于 body 尾部,仍可能出现加载阻塞或执行顺序问题,因此需要采用更高级的加载优化策略。

使用 defer 属性加载

使用 defer 属性加载

defer 是 HTML4 中定义的 <script> 标签属性,用于指示浏览器:当解析到引用外部资源的脚本时,暂时挂起脚本执行,继续并行下载资源,同时继续解析后续 HTML 文档。待整个 HTML 文档解析完毕后,再按顺序执行这些脚本。

defer 属性代码示例


<script src="outside.js" defer></script>

该属性的支持范围主要限于 IE9 及以下版本。此外,defer 的执行顺序是严格依赖的,即脚本会按照在文档中出现的顺序依次执行。

defer 顺序执行示例


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

当页面解析完成后,浏览器会严格按照顺序执行 outside1 和 outside2 文件。如果在 IE9 以下版本中使用 defer,可能会遇到执行顺序错乱的问题。为解决此兼容性问题,可以在两个脚本标签之间插入一个空的 <script> 标签作为 Hack 处理。

IE 兼容 Hack 处理


<script src="outside1.js" defer></script>
<script></script> //hack
<script src="outside2.js" defer></script>

使用 async 属性加载

使用 async 属性加载

async 是 HTML5 新增的 <script> 标签属性,代表另一种 JavaScript 加载模式。

  • 当渲染引擎解析到带有 async 属性的脚本时:
  • 浏览器会继续解析剩余的 HTML 文档,同时并行加载该脚本的外部资源。
  • 一旦脚本下载完成,浏览器会暂停 HTML 文档的解析,将控制权移交给 JS 引擎以执行该脚本。
  • 脚本执行完毕后,浏览器恢复对 HTML 文档的解析。

可以看出,async 同样可以解决加载阻塞问题。但由于其异步执行特性,脚本的执行顺序是不确定的,即先加载完成的脚本会先执行。

async 属性代码示例


<script src="outside1.js" async></script>
<script src="outside2.js" async></script>

由于谁先加载完就先执行谁,因此对于存在依赖关系的脚本,不应使用 async,而应使用 defer。虽然 defer 的兼容性较差(仅支持 IE9+),但在移动端环境中通常不存在此兼容性问题。

脚本异步加载原理与局限

脚本异步加载

脚本异步加载是许多异步加载库(如 RequireJS)所采用的基本原理。具体实现代码如下:

动态脚本加载实现


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

通过这种方式可以异步加载文件,避免阻塞页面渲染。然而,这种加载方式是无序的,无法保证依赖文件的正确加载顺序。因此,需要对上述函数进行进一步优化。

优化后的异步加载函数


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)=>{async(src);});

动态加载与并发加载对比

此外,使用脚本动态加载方式时,浏览器通常会等待 CSS 文件加载完成后才开始加载 JavaScript,这未能充分利用浏览器的并发加载优势。而使用静态 HTML 中的 async 或 defer 属性则不会出现此问题。

使用脚本异步加载时,JavaScript 的加载会等待 CSS 加载完成后才开始;而使用静态 HTML 的 async 加载时,CSS 和 JavaScript 可以并发加载。

加载策略选择与最佳实践

关于这三种加载方式的取舍,主要取决于项目目标:是需要兼容 IE8/9,还是针对移动端,亦或是桌面浏览器,或者是两者的组合。在单独使用某一种技术时,需要注意一些最佳实践建议。

1. JavaScript 文件应放置在 body 末尾; 2. 如果使用 async,建议同时加上 defer 属性以实现向下兼容。

async 与 defer 兼容写法


<script src="test.js" async defer></script> //如果两者都支持,async会默认覆盖掉defer
//如果只支持一个,则执行对应的即可

通常情况下,我们优先使用 defer 加载,因为其能保持严格的依赖顺序。

以上就是本文的全部内容,希望对大家的学习有所帮助。

作者最新文章

相关标签: