html解析是流式增量过程,浏览器边接收字节流边词法分析、生成token并构建dom,不等待全文加载;但(无async/defer)会立即中断解析,而仅阻塞渲染不阻塞解析。

HTML 解析是流式增量过程,不是“全量加载完再开始”
浏览器拿到 HTML 字节流后,Tokenizer(词法分析器)会边接收、边解码、边生成 Token,只要收到足够字节(通常几百字节),就立刻开始构建 DOM 节点。这意味着解析不会等整个 HTML 文件下载完毕——但也不是完全无条件地“一路狂奔”。关键在于:哪些标签会主动中断解析,哪些不会。
常见错误现象是误以为 <script></script> 只是“执行 JS”,其实它直接打断 DOM 构建流程;而 <link rel="stylesheet"> 虽阻塞渲染树生成,却 不阻塞 HTML 解析本身。
-
<script></script>(无async或defer):遇到即暂停解析,等待下载、编译、执行完成才继续 -
<link rel="stylesheet">:不暂停解析,但会阻塞后续的DOMContentLoaded和首次绘制(FOUC 风险下浏览器常主动延迟解析以保样式一致性) -
<img>、<iframe></iframe>:异步加载,不影响主线程解析节奏
用 async 和 defer 控制脚本对解析的干扰程度
默认同步脚本是主线程最大解析中断源。想保持 UI 响应,必须显式降级它的优先级。两者核心区别不在“是否并行下载”,而在于 何时插入执行队列:
-
async:下载完立即执行,可能在 DOM 构建中途插入,破坏节点引用(比如document.getElementById找不到后续元素) -
defer:强制等到 HTML 解析完成、DOMContentLoaded触发前执行,保证 DOM 树完整,且多个defer脚本按顺序执行
示例:<script src="a.js" defer></script><script src="b.js" defer></script> 中,b.js 一定在 a.js 后执行;而换成 async,执行顺序不确定。
预解析(preload scanner)能绕过部分主线程阻塞,但不可控
现代浏览器在主线程解析 HTML 的同时,会启用一个轻量级的后台线程做 preload scanner:它快速扫描未解析到的 HTML 片段,提前发现 <link rel="stylesheet">、<script></script>、<img> 等资源并发起预加载。这个过程不执行 JS,也不构建 DOM,所以不会触发阻塞。
但它有明显局限:
- 只识别静态资源路径,对动态拼接的
src(如src="js/"+version+".js")完全无效 - 无法预加载内联
<script>eval(...)</script>中的资源 - 预加载不等于预执行,JS 仍需等解析到对应标签才进入主线程执行队列
真正影响 UI 响应的是“长任务”,不是解析本身
很多人把卡顿归咎于“HTML 解析太慢”,其实解析本身极快(MB 级 HTML 通常几毫秒)。真正拖垮主线程的是解析后触发的同步行为:
- 大型脚本执行(如未拆分的 bundle.js 运行耗时 > 50ms)
- 大量内联 CSS 导致样式计算(
ComputedStyle)变慢 - 解析完立即运行的
document.write(已废弃但仍有遗留)
判断依据很简单:打开 Chrome DevTools → Performance 面板 → 录制页面加载 → 查看主线程火焰图中是否有持续 > 50ms 的绿色(JavaScript)或紫色(Rendering)长条。如果长任务集中在解析之后,优化点就不是 HTML 结构,而是 JS 执行策略或 CSS 规则复杂度。
最容易被忽略的是:即使你把所有脚本都设为 defer,若它们在 DOMContentLoaded 后集体执行大量 DOM 操作+重排(layout),依然会造成掉帧。这时候需要的是 requestIdleCallback 或分片处理,而不是调整 HTML 标签顺序。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











