HTML中没有blocking属性,script默认渲染阻塞;遇无async/defer的script会暂停DOM构建并执行,确保其后脚本可访问已解析元素。

HTML 中没有 blocking 属性。这是个常见误解——你在 <script> 标签上加 blocking="true" 或类似写法,浏览器会直接忽略它,脚本行为不会因此改变。
为什么 script 默认就是渲染阻塞的
浏览器解析 HTML 时,遇到 <script>(无 async 或 defer)会立即暂停 DOM 构建、下载并执行脚本,等它执行完才继续解析后续 HTML。这就是“默认阻塞”的本质——不需要额外声明。
这种机制能保证:在内联脚本或同步外链脚本里调用 document.getElementById、querySelector 等 DOM 方法时,前面的 HTML 元素大概率已入树(只要脚本写在它们后面)。
- ✅ 正确时机示例:
<div id="app"></div><script>console.log(document.getElementById("app"));</script> - ❌ 错误时机示例:
<script>console.log(document.getElementById("app"));</script><div id="app"></div>(此时元素还没解析到) - ⚠️ 注意:即使阻塞,也不能保证完整 DOMContentLoaded,比如脚本里动态插入的子元素可能还没触发相关事件
async 和 defer 会破坏默认阻塞行为
一旦用了 async 或 defer,脚本就不再阻塞 HTML 解析,执行时机变得不可控,容易导致 document.getElementById 返回 null。
立即学习“前端免费学习笔记(深入)”;
-
async:下载不阻塞,但下载完立刻执行(可能早于 DOM 构建完成) -
defer:下载不阻塞,执行推迟到 DOM 解析完毕后、DOMContentLoaded 前,但多个defer脚本按顺序执行 - 如果必须用
defer又要操作 DOM,得等DOMContentLoaded:document.addEventListener("DOMContentLoaded", () => { /* 操作 */ });
现代替代方案:useEffect + hydration 或 DOM-ready 检测
纯 HTML 场景下靠位置和默认阻塞足够;但在 SPA 或服务端渲染(SSR)中,DOM 可能已存在但 JS 尚未接管,这时不能只依赖 script 阻塞。
- React 中避免在
useEffect外直接查 DOM,因为 SSR 时document不存在 - 需要 DOM 操作时,优先用
useEffect(() => { /* 操作 */ }, []),它确保只在客户端执行 - 原生场景可封装简单检测:
if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", fn); } else { fn(); } - 注意:
document.readyState的"interactive"状态不等于 DOM 完整可用,仍建议用"complete"或事件监听
真正关键的不是加什么属性,而是理解浏览器解析流程与脚本执行时机的耦合关系。很多人卡在“为什么 DOM 找不到”,其实问题不在属性漏写,而在脚本位置放错了、或误用了 async 却没补事件监听。



















