是的,不加async或defer的一定阻塞dom解析;浏览器会暂停html解析,同步下载、解析并执行脚本,导致白屏、dom操作失败及首屏延迟。

不加 async 或 defer 的 <script></script> 一定阻塞 DOM 解析
浏览器从上到下解析 HTML,遇到没加 async 或 defer 的 <script src="a.js"></script>,会立刻暂停 DOM 构建,同步下载、解析、执行——哪怕脚本内容只有一行 console.log(1)。此时 document.getElementById("main") 必然返回 null,首屏白屏时间直线上升。
常见错误场景:
-
里放多个无属性脚本,页面长时间空白 - 内联脚本写在
开头,但想操作后面才出现的元素 - 脚本体积小但网络慢(如 TTFB > 200ms),成为关键路径瓶颈
实操建议:
- 杜绝在
中直接写无属性外链脚本 - 内联脚本若需操作 DOM,必须放在目标元素之后,或包裹在
DOMContentLoaded回调中 - 用 Chrome DevTools → Network 面板确认脚本是否卡住 HTML 解析(看 “HTML” 资源加载是否被拖长)
async 只解决下载不阻塞,执行仍会中断 DOM 解析
async 让脚本下载与 HTML 解析并行,但一旦下载完成,浏览器会立即中断当前解析流程,执行该脚本——不管 DOM 是否构建完毕、其他脚本是否就绪。
典型问题:
-
document.querySelector("header")报null,因为脚本执行时<header></header>还没被解析到 - 两个
async脚本:A 依赖 B 定义的lodash,但 A 先下完先执行,报ReferenceError -
<script async>console.log("hi")</script>会被浏览器忽略(async对内联脚本无效)
适用场景仅限:
- 完全独立、不读写 DOM、不依赖其他 JS 的代码(如统计埋点、错误监控 SDK)
- 广告脚本、第三方分析工具
defer 是依赖 DOM 的脚本唯一安全选择
defer 让脚本异步下载,但推迟执行到整个 HTML 解析完成、DOMContentLoaded 触发前,并严格按 <script></script> 在文档中的书写顺序执行。
注意点:
-
defer只对外链脚本有效(src属性必须存在),<script defer>...</script>会被忽略 - 多个
defer脚本即使下载完成时间不同,也强制排队执行:a.js没执行完,b.js不会启动 - 如果某个
defer脚本加载极慢(如 CDN 故障),DOMContentLoaded会被拖住——它等所有defer脚本下载+执行完毕才触发
典型用法:
- 初始化导航菜单、绑定表单事件(需完整 DOM)
-
utils.js和app.js有明确依赖关系 - 替代手动写
document.addEventListener("DOMContentLoaded", ...)
type="module" 默认行为类似 defer,但更严格
<script type="module" src="main.js"></script> 默认具有 defer 语义:异步下载、DOM 解析完成后按序执行,且自动启用严格模式、支持 import/export。
关键差异:
- 模块脚本天然隔离作用域,不会污染全局(不像传统脚本默认挂载
window) - 模块之间依赖靠
import声明,执行顺序由依赖图决定,不是靠 HTML 位置 - 模块脚本不支持
async和defer属性(设了也无效) - 模块脚本对跨域资源要求
CORS头,普通脚本则无此限制
兼容性提醒:IE 完全不支持,type="module" 需配合 nomodule fallback 使用。
真正容易被忽略的点是:async 和 defer 都不能“消除”脚本执行对渲染的影响——它们只是把阻塞从“下载阶段”转移到“执行阶段”,或干脆移到 DOM 构建之后。能否安全操作 DOM,不取决于是否异步加载,而取决于执行时机是否晚于 DOM 就绪。这个边界,得靠 DOMContentLoaded 或 defer 来锚定,不是靠“快”就能绕过去。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











