defer属性仅对外部脚本(含src)生效,内联脚本或src为空/空格/带值的defer均被浏览器静默忽略,且必须为无值布尔属性;多个defer脚本严格按html顺序执行。

defer属性只对带src的外部脚本生效
浏览器明确要求 defer 必须配合 src 属性才能触发延迟执行逻辑。只要 script 标签没有 src,哪怕写了 defer,浏览器也完全忽略它——包括内联脚本、空 src=""、src=" "(含空格)等写法。
常见错误现象:
-
<script defer>console.log('hi')</script>→ 立即执行,和没写defer一样 -
<script src="" defer></script>→ 同步阻塞解析,defer静默失效 - 构建工具注入的初始化脚本(如
window.__INITIAL_STATE__ = {...})被包裹在<script defer></script>中 → 实际仍同步执行
defer必须是无值布尔属性
defer 是 HTML 布尔属性,只认“存在与否”,不接受任何赋值。写成 defer="true"、defer="false"、defer="defer" 全部无效——浏览器会解析为存在该属性,但行为退回到默认同步模式。
真正起作用的写法只有一种:<script src="a.js" defer></script>。其他所有变体都不触发延迟逻辑,且不会报错、不发 warning,属于典型的“静默失效”。
async 和 defer 同时出现时 defer 失效
只要一个 script 标签同时声明了 async 和 defer,浏览器会直接忽略 defer,按 async 规则处理:谁先下载完谁先执行,顺序不保证。
典型误写:
-
<script src="utils.js" async defer></script>→ 等价于只写async - 多个脚本中部分加
async、部分加defer→ 混合加载队列,顺序无法保障
有依赖关系的脚本(如先加载 lodash.js,再加载 app.js),必须全部统一用 defer,且严格按依赖顺序书写。
模块脚本(type="module")自带 defer 语义
<script type="module" src="main.js"></script> 默认具备 defer 行为:并行下载、DOM 就绪后执行、按声明顺序运行。显式加上 defer 不仅冗余,还可能干扰构建工具(如 Webpack 的依赖分析)或导致 runtime 误判。
更关键的是:type="module" 脚本和普通 defer 脚本不在同一执行队列。如果 HTML 中混用:
<script src="legacy.js" defer></script><script type="module" src="new.js"></script>
两者执行时机接近但不同步,不能互相假设对方已完成初始化。
最容易被忽略的点是:所有失效场景都不会在控制台报错,也不会在网络面板里暴露异常;你只能靠检查最终生成的 HTML 是否满足「src 存在 + defer 无值」这两个硬条件来确认是否真正在起作用。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











