script 标签的异步加载:async、defer、type="module" 全面解析
本文主要是分享一下,关于加载script多种方式及区别
- 1.同步加载:
<script src="index.js"></script> - 2.
defer异步加载:<script defer src="index.js"></script> - 3.
async异步加载:<script async src="index.js"></script> - 4.
module异步加载:<script type="module" src="index.js"></script> - 5.
module async异步加载:<script type="module" src="index.js" async></script>
众农周知,浏览器解析
html时,如果遇到使用<script src="index.js"></script>加载js时,html的解析会被阻塞,这个时候会先去下载index.js,等待下载完并执行完js之后,然后再继续解析html。这样的话,如果js下载时间过长,无疑会增加页面的渲染时间,那么,有没有啥办法可以让解析html与下载js并行执行呢?有的,并且提供了多种。
1.五种加载方式深度解析
1.1 同步加载 <script src="index.js"></script>
特点
- 阻塞解析html
- 须等加载完js并执行完之后才能继续解析html
- 增加首屏渲染时间
不推荐使用
适用场景
- 传统的加载script方式,需要兼容低版本浏览器
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- 同步加载且阻塞解析html -->
<script src="index.js"></script>
</head>
<body>
</body>
</html>
1.2 异步加载,使用 defer属性
特点
- 并行下载js
- 不阻塞解析html,解析html与下载并行
- 在html解析完成
后,DOMContentLoaded事件触发前执行 - 多个script时,异步加载,按顺序执行
适用场景
- 加载的js存在依赖关系,须按顺序执行
- 加载的js需要操作
DOM - 常见vue、react项目中,构建工具已自动处理,编译后自动添加
defer属性
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" href="/test/favicon.ico" />
<title>xxxxx管理系统</title>
<!-- 异步加载,优先执行第三方依赖 -->
<script defer="defer" src="/test/js/chunk-vendors.0821bbb1.js"></script>
<!-- 异步加载,上面的js执行完再执行 -->
<script defer="defer" src="/test/js/app.566a7ad5.js"></script>
<link href="/test/css/chunk-vendors.ff7db5f3.css" rel="stylesheet" />
<link href="/test/css/app.0d1bd94e.css" rel="stylesheet" />
</head>
<body>
<noscript
><strong
>System doesn't work properly without JavaScript enabled. Please enable
it to continue.</strong
></noscript
>
<div id="app"></div>
</body>
</html>
1.3 异步加载,使用 async属性
特点
- 并行下载js
- 下载阶段,不阻塞解析html,解析html与下载并行
下载完成后马上执行- 多个script时,异步加载,
谁先下载完谁就执行 - 可能阻塞解析html,如果解析html完成前就已经下载完了,那就会执行js,阻塞解析html
- 可能会阻塞
DOMContentLoaded事件的触发时机 当js在DOMContentLoaded事件触发前完成下载并执行,就会延长DOMContentLoaded的触发时间,js的执行会阻塞主线程,执行完才会触发DOMContentLoaded事件
适用场景
- 加载的js没有赖关系,单独执行
- 加载的js
无需操作DOM - 常用于分析、广告、埋点脚本加载
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" href="/test/favicon.ico" />
<title>xxxxxx官网</title>
<!-- 异步加载,独立运行,无脚本依赖 -->
<script async src="https://www.guanggao.com/gg/js?id=GG_1"></script>
<script async src="https://www.guanggao.com/gg/js?id=GG_2"></script>
</head>
<body>
<noscript
><strong
>System doesn't work properly without JavaScript enabled. Please enable
it to continue.</strong
></noscript
>
<div id="app"></div>
</body>
</html>
1.4 异步加载,使用 type="module"属性
特点与defer差不多
- 不阻塞解析html,解析html与下载并行
- 在html解析完成
后,DOMContentLoaded事件触发前执行 - 多个script时,异步加载,按顺序执行
- 与
defer主要差异是,使用esm模块化,且默认开启严格模式
适用场景 与defer差不多
- 加载的js存在依赖关系,须按顺序执行
- 加载的js需要操作
DOM 使用esm开发的脚本常见使用vite构建的vue、react项目中,编译后自动添加type="module"属性
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" href="/test/favicon.ico" />
<title>xxxxxx系统</title>
<!-- 异步加载,使用esm开发 -->
<script type="module" crossorigin src="/assets/index-BmxZ5FsG.js"></script>
</head>
<body>
<noscript
><strong
>System doesn't work properly without JavaScript enabled. Please enable
it to continue.</strong
></noscript
>
<div id="app"></div>
</body>
</html>
1.5 异步加载,使用 type="module" async属性
特点与async差不多
- 并行下载js
- 下载阶段,不阻塞解析html,解析html与下载并行
下载完成后马上执行- 多个script时,异步加载,
谁先下载完谁就执行 - 可能阻塞解析html,如果解析html完成前就已经下载完了,那就会执行js,阻塞解析html
- 可能会阻塞
DOMContentLoaded事件的触发时机 当js在DOMContentLoaded事件触发前完成下载并执行,就会延长DOMContentLoaded的触发时间,js的执行会阻塞主线程,执行完才会触发DOMContentLoaded事件 与async主要差异是,使用esm模块化,且默认开启严格模式``
适用场景 与async差不多
- 加载的js没有赖关系,单独执行
- 加载的js
无需操作DOM - 常用于分析、广告、埋点脚本加载
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" href="/test/favicon.ico" />
<title>xxxxxx官网</title>
<!-- 异步加载,使用esm开发,独立运行,无脚本依赖 -->
<script type="module" async src="https://www.guanggao.com/gg/js?id=GG_1"></script>
</head>
<body>
<noscript
><strong
>System doesn't work properly without JavaScript enabled. Please enable
it to continue.</strong
></noscript
>
<div id="app"></div>
</body>
</html>
2.总结
2.1.加载script的5种方式:
- 同步-阻塞解析html:
<script> - 异步-不阻塞解析html-按顺序执行:
<script defer> - 异步-不阻塞解析html-无序下载完立即执行:
<script async> - 异步-esm-不阻塞解析html-按顺序执行:
<script type="module"> - 异步-esm-不阻塞解析html-无序下载完立即执行:
<script type="module" async>
2.2.各方式差异:
| 加载方式 | 执行时机 | 是否阻塞HTML解析 | 执行顺序 | 适用场景 |
|---|---|---|---|---|
<script> |
立即执行 | 是 | 文档顺序 | 传统脚本 |
<script defer> |
DOM解析后 | 否 | 文档顺序 | 依赖DOM的脚本 |
<script async> |
下载完成后 | 下载不阻塞,执行可能阻塞 | 无序 | 独立第三方脚本 |
<script type="module"> |
DOM解析后 | 否 | 文档顺序 | ES模块化代码 |
<script type="module" async> |
下载完成后 | 下载不阻塞,执行可能阻塞 | 无序 | 独立ES模块 |
2.3.加载流程图:

2.4.使用建议:
- js使用非esm,优先考虑使用
defer - js使用esm,优先考虑使用
type="module"