默认不加属性的会阻塞html解析;defer仅对外部脚本生效,需同时满足依赖dom和执行顺序依赖两个条件;async适用于不读写dom且无依赖的自包含脚本,执行时机不可控。

默认不加任何属性的 <script></script> 就会阻塞 HTML 解析,这是首屏白屏最常见原因——哪怕脚本只有 2KB,也可能让 FCP 推迟 300ms 以上。
什么时候该用 defer?只看两个硬条件
脚本必须同时满足:① 依赖 DOM(比如调 document.getElementById、addEventListener);② 与其他脚本有执行顺序依赖(如先加载 jquery.js,再加载 plugin.js)。
-
defer只对外部脚本生效(即带src属性),<script defer>init();</script>中的defer被浏览器完全忽略 - 多个
defer脚本严格按 HTML 中出现顺序执行,且一定在DOMContentLoaded触发前完成 - 它不改变下载时机——脚本仍从解析到该标签时就开始下载,只是推迟执行
- IE10+ 全支持,但旧版 Safari 对混用
defer和type="module"的顺序处理略有差异
什么时候能用 async?必须确认“三不”
脚本得做到:不读 DOM、不写 DOM、不依赖其他 JS 执行结果。典型场景只有埋点、广告 SDK、错误监控等自包含逻辑。
-
async同样只对外部脚本有效,<script async>console.log(1)</script>的async无效 - 执行时机不可控——谁先下载完谁先跑,可能在
都没解析到时就执行,document.querySelector('main')必然报TypeError: Cannot read property 'querySelector' of null - IE9 及更早版本不支持
async,若需兼容,只能降级为底部同步加载 - 不要给有
import或require的打包产物加async,Webpack/Vite 输出的main.js应统一用defer
内联脚本和动态插入的坑别踩
所有“延迟执行”的属性对内联脚本都无效,而动态插入的 script 默认行为接近 async,不是 defer。
- 把初始化配置写成
<script defer>window.APP_CONFIG = {...}</script>是白忙——它立刻执行,且阻塞解析 - 动态创建:
const s = document.createElement('script'); s.src = 'a.js'; document.head.appendChild(s);,这个脚本下载不阻塞,但执行时机由浏览器决定,不保序、不等 DOM - 想让动态脚本等 DOM 就绪,得手动监听:
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', load) : load() - SSR 页面中,服务端注入的
window.__INITIAL_STATE__必须出现在所有defer脚本之前,且自身不能加defer
最容易被忽略的是:很多人以为把 defer 加在内联脚本上就能“延迟”,结果既没延迟也没解阻塞。真正起效的只有 <script src="xxx" defer></script> 这一种写法,其余全是幻觉。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











