JavaScript 文件加载优化:async、defer 与动态脚本详解
本文深入解析 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 加载,因为其能保持严格的依赖顺序。
以上就是本文的全部内容,希望对大家的学习有所帮助。
JS处理特殊字符时怎么安全生成JSON
2026-11-05 15:44:51
内部脚本与外部js文件的区别及优缺点对比
2026-11-05 14:42:19
JavaScript前端性能优化指南:提升页面加载速度与渲染效率
2026-11-05 13:48:26
主流JS反混淆工具对比:de4js、unminify与AST还原方案评测
2026-11-05 12:00:56
Nuxt.js 性能优化技巧:代码分割与组件懒加载
2026-11-03 23:32:56
相关标签:
