defer仅保证同级外部脚本按声明顺序执行,不处理模块导入、动态加载或跨文件依赖;它不等待import()、fetch()等异步操作完成,也无法协调css阻塞链或ssr hydration时序。

HTML 中没有“资源加载顺序属性”能直接调整复杂依赖链的执行时序——async、defer、fetchpriority、loading 各管一摊,混用反而破坏预期。真要控制依赖链,得靠组合策略+运行时协调。
为什么 defer 不能解决多层 JS 依赖问题
defer 只保证同级外部脚本按声明顺序执行,不处理跨文件的模块依赖或动态引入场景。比如 vendor.js 里导出一个函数,app.js 里调用它,看似 defer 能兜住;但若 vendor.js 内部又通过 import() 加载了 utils.js,而 app.js 在 utils.js 还没 resolve 就执行,照样报错。
-
defer不等待import()、fetch()或 DOM 就绪以外的任何异步操作 - 多个
defer脚本之间无依赖感知,A 依赖 B,但 B 的onload回调还没触发,A 就已开始执行 - 服务端渲染(SSR)中,
defer脚本可能在 hydration 前就执行,导致状态不一致
用 link rel="preload" 提前抢带宽,但别让它乱序
preload 是少数能在 HTML 解析早期就发起请求的手段,但它只管下载,不管执行。如果预加载了 lodash.js,但业务脚本在它之前就执行,依然会 ReferenceError。
- 必须配对使用:
<link rel="preload" as="script" href="lodash.js">+<script src="lodash.js" defer></script>,且两个href字符串完全一致(包括大小写和查询参数) - 不要给非首屏 JS
preload:它会抢占关键资源带宽,Chrome Network 面板中看到 Priority 列变成 Highest,但实际拖慢 FCP -
as="script"必须显式声明,漏写会导致浏览器降级为as="fetch",优先级回落为 Medium
动态加载时用 sessionStorage 维护执行队列
当依赖链来自用户行为(如点击后加载图表库 → 渲染组件 → 请求数据),静态 HTML 属性完全失效,必须靠运行时状态管理。
- 把待加载脚本路径存进
sessionStorage,例如["chart.js", "echarts.min.js", "renderer.js"] - 每次成功执行一个脚本后,从数组头部
shift()并更新存储,避免刷新后重复执行 - 用
Promise链串联加载过程:loadScript(a).then(() => loadScript(b)),比单纯靠defer更可控 - 注意:不要在
DOMContentLoaded前大量动态插入script,否则会阻塞解析,抵消懒加载收益
最容易被忽略的坑:CSS 和 JS 的隐式阻塞链
你以为只改了 JS 加载方式就完了?其实 <link rel="stylesheet"> 默认阻塞 HTML 解析,而它内部的 @import 又会阻塞自身解析——这构成一条隐式依赖链,比 JS 更难调试。
- 一个
main.css里写了@import "theme.css",那么theme.css不仅延迟加载,还会让后续所有defer脚本推迟执行 - 即使你把 JS 放在
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











