文章中英模式
常见的前端面试题目 - 页面加载 - Script 位置与 defer、async 属性
深入解析 JavaScript Script 标签的最佳放置位置,以及 defer 与 async 属性的使用时机与效果,帮助你提升网页性能与用户体验。
文章中英模式

懒得看文章?那就来看视频吧
Script 标签的位置
网页中 JavaScript 的加载与执行时机直接影响用户体验。根据摆放位置的不同,script 标签的行为也有所差异:
- 1.放在
<head>中:浏览器会在解析 HTML 前先下载并执行 JavaScript,这会阻塞页面渲染,导致页面加载速度变慢。 - 2.放在
</body>前:浏览器会先渲染完 HTML 内容,再下载并执行 JavaScript,用户可以更快看到页面内容,但可能在 JavaScript 加载前无法使用某些功能。
defer 属性的用途
defer 属性让脚本的下载与 HTML 解析同时进行,但会延迟执行直到 HTML 解析完成:
<script defer src="script.js"></script>defer 的特点:
- •不会阻塞 HTML 解析与渲染
- •保证按照在 HTML 中的顺序执行
- •在 DOMContentLoaded 事件之前执行
- •仅对外部脚本(有 src 属性)有效
适用场景:
- •需要 DOM 完整加载后才能执行的脚本
- •依赖于其他脚本顺序的执行
- •不急需立即执行的脚本
module 脚本的用途
type="module" 让浏览器将脚本视为 JavaScript 模块,支持 import/export 语法:
<script type="module" src="app.js"></script>module 与 defer 的区别:
- •module 脚本预设具有 defer 行为,无需额外添加 defer 属性
- •module 支持 import/export 语法,而 defer 脚本不支持
- •module 脚本自动采用严格模式 (strict mode)
必须使用 module 而非 defer 的场景:
- •需要使用 import/export 语法时
- •需要模块化代码以提高可维护性
// math.js (模組)
export function add(a, b) {
return a + b;
}
// app.js (使用模組)
import { add } from './math.js';
document.addEventListener('DOMContentLoaded', () => {
console.log(add(2, 3)); // 輸出: 5
});async 属性的用途
async 属性让脚本的下载与 HTML 解析同时进行,下载完成后立即执行:
<script async src="script.js"></script>async 的特点:
- •不会阻塞 HTML 解析与渲染
- •不保证执行顺序,先下载完的先执行
- •执行时会暂停 HTML 解析
- •仅对外部脚本有效
- •不一定在 DOMContentLoaded 事件前执行
适用场景:
- •独立的、不依赖其他脚本的功能
- •分析脚本、广告脚本等第三方脚本
- •不需要操作 DOM 的脚本
图解比较
以下图解展示了不同脚本加载方式的行为差异:
──────────────────────────── 时间轴 ────────────────────────────>
【无属性 - 放在 </body> 前】
HTML 解析: ■■■■■■■■■■■■■■■■■■■■■■■■■■HTML解析■■■■■■■■■■■■■■■■■■
↑ ↑
开始解析 解析完成
脚本处理: ■■■下载+执行■■■
↑ ↑
开始下载 执行完成
<body>
<!-- 内容 -->
<script src="script.js"></script> <!-- 传统做法 -->
</body>
--------------------------------
【defer】
HTML 解析: ■■■■■■■■■■■■■■■■■■■■■■■■■■HTML解析■■■■■■■■■■■■■■■■■■→DOM就绪
↑ ↑ ↑
开始解析 解析完成 DOMContentLoaded
脚本处理: ■■■■■■■■■■■■■下载■■■■■■■■■■■■■ ■■■执行■■■
↑ ↑ ↑ ↑
开始下载 下载完成 开始执行 执行完成
<head>
<script defer src="script.js"></script> <!-- 现代推荐做法 -->
</head>
--------------------------------
【async】
HTML 解析: ■■■■■■■■■■■■■■■■■暂停■■■■■■■■■剩余HTML解析■■■■■■■■■■■
↑ ↑ ↑ ↑
开始解析 暂停 继续解析 解析完成
脚本处理: ■■■■■■■下载■■■■■■■■■执行■■■
↑ ↑ ↑
开始下载 下载完成 执行完成
<head>
<script async src="script.js"></script> <!-- 适用于独立脚本 -->
</head>最佳实践
- 1.现代推荐做法:将带有
defer属性的脚本放在<head>中<head> <script defer src="main.js"></script> </head>这样可以尽早开始下载脚本,同时不阻塞 HTML 解析,提升页面加载体验。
- 2.独立脚本:使用
async属性<head> <script async src="analytics.js"></script> </head> - 3.传统做法:将脚本放在
</body>前<body> <!-- 頁面內容 --> <script src="main.js"></script> </body> - 4.核心功能且时间敏感:不使用属性,直接放在
<head>中<head> <script src="critical.js"></script> </head>
🔥 常见面试题目
(一)defer 和 async 有什么区别?
解答:两者主要区别在于执行时机和顺序:
- •
defer:在 HTML 解析完成后按照在 HTML 中的顺序执行 - •
async:下载完成后立即执行,不保证顺序,会中断 HTML 解析
<!-- defer 示例:按顺序执行 -->
<head>
<script defer src="first.js"></script>
<script defer src="second.js"></script>
<!-- first.js 一定会在 second.js 之前执行 -->
</head>
<!-- async 示例:不保证顺序 -->
<head>
<script async src="analytics.js"></script>
<script async src="ads.js"></script>
<!-- 哪个先下载完成就先执行哪个 -->
</head>(二)为什么不建议将所有脚本都放在 <head> 中且不使用 defer 或 async?
解答:这会造成浏览器必须先下载并执行所有 JavaScript 才开始渲染页面,导致用户看到白屏时间延长,严重影响首次内容绘制(FCP)和用户体验。
<!-- 不推荐:会阻塞渲染 -->
<head>
<script src="large-library.js"></script>
<script src="app.js"></script>
<script src="components.js"></script>
<!-- 页面会等待所有脚本下载和执行完才开始渲染 -->
</head>
<!-- 推荐:使用 defer -->
<head>
<script defer src="large-library.js"></script>
<script defer src="app.js"></script>
<script defer src="components.js"></script>
<!-- HTML 解析不会被阻塞,脚本会在 DOMContentLoaded 前执行 -->
</head>(三)何时选择 defer,何时选择 async?
解答:
- •选择
defer:当脚本需要操作 DOM 或依赖其他脚本的执行顺序 - •选择
async:当脚本完全独立,不需要保证顺序,如分析工具、广告等
<!-- 使用 defer 的情境:需要 DOM 和执行顺序 -->
<head>
<script defer src="jquery.js"></script>
<script defer src="jquery-plugin.js"></script>
<script defer src="app.js">
// 这个脚本依赖 jquery 和 jquery-plugin
// 且可能需要操作 DOM 元素
</script>
</head>
<!-- 使用 async 的情境:独立功能 -->
<head>
<script async src="google-analytics.js"></script>
<script async src="facebook-pixel.js"></script>
<!-- 这些脚本彼此独立,不需要特定顺序,也不依赖 DOM -->
</head>(四)module 脚本(type="module")的加载行为如何?
解答:module 脚本预设就具有 defer 的行为,即使没有显式设定 defer 属性。如果想要 async 行为,需要明确添加 async 属性。
<!-- 预设具有 defer 行为 -->
<head>
<script type="module" src="app.js"></script>
<!-- 等同于 <script type="module" defer src="app.js"></script> -->
<!-- If async behavior is needed, must be explicitly specified -->
<script type="module" async src="independent-module.js"></script>
</head>(五)inline script 可以使用 defer 或 async 吗?
解答:不可以。defer 和 async 属性仅对外部脚本(有 src 属性的 script 标签)有效,对内联脚本没有效果。
<!-- 无效:defer 和 async 对内联脚本无效 -->
<head>
<script defer>
console.log("这个 defer 属性不起作用");
// 这个脚本会立即执行,阻塞 HTML 解析
</script>
<script async>
console.log("这个 async 属性不起作用");
// 这个脚本也会立即执行
</script>
<!-- 有效:外部脚本可以使用 defer 或 async -->
<script defer src="external.js"></script>
</head>