defer仅对带src属性的外部脚本生效,内联脚本加defer无效;其执行时机为html解析完成、dom构建完毕后且domcontentloaded事件触发前,严格按html中书写顺序执行,适用于有依赖关系的外部脚本。

defer只对带src的外部脚本生效
内联脚本加defer完全没用,浏览器会直接忽略。比如<script defer>console.log(1)</script>里的defer属性形同虚设。
真正起作用的写法必须同时满足两个条件:有src属性 + 声明defer。例如:<script src="app.js" defer></script>。
- 原因在于
defer的设计目标是“不阻塞 HTML 解析,但保持执行顺序”,而内联脚本已经嵌入文档流,不存在“加载延迟”问题 - 如果你需要延迟执行内联逻辑,改用
document.addEventListener('DOMContentLoaded', ...)或setTimeout更可靠 - 构建工具(如 Webpack)输出的模块代码,即使用了
type="module",也不自动继承defer的执行时序保证——仍需显式加defer
多个defer脚本严格按HTML中书写顺序执行
这是defer和async最本质的区别。哪怕b.js比a.js先下载完,只要它们都带defer,且<script src="a.js" defer></script>写在前面,就一定先执行a.js再执行b.js。
- 适合有明确依赖关系的场景,比如
lodash.js→utils.js→main.js - 混用
async或无属性脚本会打破这个顺序——defer只约束其他defer脚本 - IE10+支持
defer,但不支持type="module"和defer组合(旧版 IE 直接跳过type="module"脚本)
defer执行时机:DOM就绪后、DOMContentLoaded前
defer脚本不是等window.onload(所有资源加载完),也不是一下载完就跑,而是在整个HTML解析完成、DOM树建好之后,但在DOMContentLoaded事件触发之前执行。
- 这意味着你可以安全访问
document.getElementById('xxx'),不用担心元素还没生成 - 但它本身会参与触发
DOMContentLoaded——如果它是最后一个推迟执行的脚本,该事件会在它执行完后立即发出 - 注意:如果某个
defer脚本里写了同步死循环或长时间运算,会拖慢DOMContentLoaded触发,进而影响后续监听器
TensorFlow这类大体积脚本特别适合defer
像@tensorflow/tfjs这种1MB+的库,放默认<script src="..."></script>里会直接阻塞HTML解析,导致<canvas></canvas>等元素迟迟无法渲染,页面白屏几百毫秒。
- 改成
<script src="tf.min.js" defer></script>后,浏览器一边建DOM一边后台下载,等<canvas id="output-canvas"></canvas>已挂载完毕,再执行后续逻辑 - 搭配
model-loader.js也加defer,就能保证它在TF加载完成后才运行,且能拿到真实DOM节点 - 别指望
defer能解决所有问题——如果脚本内部还发起大量异步请求或做重计算,首屏交互延迟仍可能明显
defer不等于“等页面完全加载好”,它只等DOM解析完;而很多开发者误以为它会等图片、字体、CSSOM全部就绪,结果在脚本里访问getBoundingClientRect()发现尺寸为0——那是因为样式还没计算完,得靠requestAnimationFrame或load事件兜底。**前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











