document.querySelector在<head>中返回null并非DOM未就绪,而是CSSOM未构建完成;浏览器需CSSOM才能确定元素可见性、样式和布局参与权,故渲染树构建被阻塞。

document.querySelector 在 <head> 里执行却返回 null?不是 DOM 没建好,是 CSSOM 还没就绪——渲染树构建阶段,浏览器强制等待 CSSOM 完成,否则无法确定元素是否可见、用什么样式、要不要参与布局。
为什么 DOMContentLoaded 有时等得特别久
这个事件不只看 DOM 解析完没完,更要看“它前面有没有同步 CSS”。如果 <link rel="stylesheet"> 出现在 <script> 前面,DOMContentLoaded 就必须等到该 CSS 下载、解析、CSSOM 构建完成才触发。
- DOM 解析器照常跑,节点也创建了,但 JS 引擎被挂起,脚本暂停执行
- 哪怕 CSS 文件只是慢一点(比如 CDN 节点抖动或 TLS 握手延迟),整个首屏 JS 初始化就被拖住
- 内联
<style>不触发网络请求,所以阻塞窗口短且可控;外部<link>则完全依赖网络链路质量 - Chrome 的预加载扫描器会提前下载 CSS,但不会提前解析——它只加速“拿到字节”,不加速“变成 CSSOM”
media="print" 是绕过初始 CSSOM 阻塞最轻量的手段
把非关键 CSS 的 media 设为 "print",浏览器解析时会跳过它构建 CSSOM,不阻塞渲染树构建;等页面首屏绘制完成后,再切回 media="all" 激活。
- 写法:
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'"> - 注意:不能用
onload直接改rel或删标签,必须改media才能触发重计算 - 该技巧对
@import无效——@import是 CSS 内部语法,解析器必须先加载并解析外层 CSS 才能看到它,无法被media规避 - 若 CSS 里有
@font-face,字体仍可能延迟加载,需配合font-display: swap避免 FOUC
document.styleSheets 不等于 CSSOM 就绪
你能在脚本里读到 document.styleSheets.length > 0,不代表所有样式规则都已解析完毕,尤其当存在嵌套 @import 或跨域 CSS 时。
立即学习“前端免费学习笔记(深入)”;
-
link.addEventListener('load', ...)只表示资源字节下载完成,不保证 CSSOM 构建结束 -
document.styleSheets[i].cssRules可能为null或空数组,特别是在跨域 CSS(未设 CORS)或解析出错时 - 真正可靠的判断方式是监听
document.fonts.ready(仅限字体)、或用MutationObserver监听<style>标签内容变化,但无法覆盖全部场景 - 调试时别只查 DOM 节点是否存在,要验证
getComputedStyle(el).display !== 'none'和el.offsetParent !== null才算“真正激活”
最易被忽略的是:CSS 阻塞的从来不是 DOM 节点创建,而是“节点是否进入渲染流程”的判定权。你看到的空白、错位、offsetHeight === 0,往往不是 JS 写错了,是浏览器在等那一份还没落地的 CSSOM。



















