切换至深色主题
HTML文档工作原理
掌握网页运行实现原理的重要性
网页性能优化的基础,就是通过分析网页整个生命周期出现的问题而灵活采取相关的定向性能优化。
- Document:visibilitychange 事件: https://developer.mozilla.org/zh-CN/docs/Web/API/Document/visibilitychange_event
- Navigator.sendBeacon(): https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/sendBeacon
- 页面生命周期 API: https://developer.chrome.com/docs/web-platform/page-lifecycle-api?hl=zh-cn#developer-recommendations-for-each-state
- PageLifecycle.js: https://github.com/GoogleChromeLabs/page-lifecycle
网页资源加载机制
加载机制是浏览器中很重要的实现机制,一个网页的生命周期,大部分的时间都消耗在加载资源的环节。
同步加载
即多个资源的加载时机依赖于资源的先后顺序。
异步加载
即多个资源的加载时机不依赖于资源的先后顺序。
并行加载
即多个资源的加载时机能够实现同时加载。
延迟加载
延迟加载是一种在页面加载时推迟加载非关键资源的技术。
网页资源执行机制
执行机制是浏览器中很重要的实现机制,当网页资源加载后,浏览器就会对资源进行解析、编译等相关事务处理,最后实现渲染绘制。
注意事项
当样式表仍在加载和解析时,Firefox 会阻止所有脚本。WebKit 仅在脚本尝试访问可能受卸载样式表影响的某些样式属性时才阻止脚本。
同步执行
即多个任务的执行时机依赖于事务队列的先后顺序。
异步执行
即多个任务的执行时机不依赖于事务队列的先后顺序。
延迟执行
即多个任务的执行时机滞后于部分任务的执行。
并行执行
即多个任务的执行时机同时执行,互不影响。
显示器
vsync(垂直同步)帧
硬件起源:显示器的 V‑Sync / Vertical Synchronization 老式 CRT 显示器,电子束从上往下一行一行扫描画面,扫完一整屏,会发出一个垂直同步信号 vsync,代表:这一屏画完了,可以准备下一帧新画面。 现在液晶显示器没有电子束,但保留这套逻辑:
vsync 信号:显示器通知主机,“我准备接收下一帧画面了” 的脉冲信号。
常见 60Hz 显示器:每秒发出 60 次 vsync,间隔 ≈ 16.66ms。 120Hz:间隔 ≈ 8.33ms。
浏览器 vsync 帧(渲染帧)
浏览器跟随显示器的 vsync 脉冲做渲染。 每收到一次 vsync 信号,浏览器开启一次渲染帧周期,完整一帧工作顺序:
- 执行
requestAnimationFrame(rAF)回调 - 执行 Layout(布局 / 重排)
- 执行 Paint(绘制)
- Composite(图层合成,交给 GPU 输出画面到显示器)
⚠️ 主线程已经空闲,DOM、CSSOM 全部就绪,但是没有 vsync 信号,浏览器不会执行 Layout/Paint,就原地等待,等到 16.66ms vsync 来了,才做渲染。
渲染主线程
HTML 解析、JS 执行、渲染任务,全都跑在同一个渲染主线程
HTML文档工作全链路
HTML文档解析
├─ 下载HTML文档
├─ 开始解析HTML
│ ├─ 逐行构建DOM树
│ ├─ 遇到 <link css>
│ │ ├─ 开启网络请求下载CSS
│ ├─ 继续解析HTML,不阻塞
│ ├─ 遇到普通同步<script>
│ │ ├─ 暂停HTML解析,如果是外部JS:发起下载
│ │ ├─ ⚠️如果前置有未加载完成的CSS,等待CSS加载完成
│ │ │ ├─ 🔴 JS 可以读取元素布局样式(offsetWidth、getComputedStyle),如果样式没加载完,拿到的样式是错误的。
│ │ ├─ 执行JS
│ │ ├─ JS执行完毕 → 恢复HTML解析
├─ ✅ 所有HTML解析完成,DOM树构建完成
├─ 执行所有defer脚本
├─ ✅ 触发 DOMContentLoaded
├─ 继续加载图片、字体等资源
└─ ✅ 所有资源加载完成 → window.load渲染
Blink 浏览器源码层面调度倾向: HTML 解析可以持续产出 DOM,尽快构建更多内容; 渲染可以延后批量执行,减少多次 Layout/Paint 带来的性能开销。 浏览器选择吞吐量优先,而非 “尽早渲染优先”。
浏览器拥有 “阶段性暂停解析、执行渲染” 的调度能力,这是渐进渲染的基础; 但这个窗口,只会在Parser 正常持续运行、没有被同步脚本强制打断时才有机会出现; 一旦被同步 script 切割解析流程:脚本执行完成后,主线程优先恢复 HTML 解析,持续占住主线程,渲染任务持续排队,渐进渲染机制无法触发;
HTML 解析还在持续进行,主线程正在被解析任务占用。什么时候能渲染? 浏览器内部有调度策略: 当解析一段之后,会短暂暂停 Parser,触发一次机会执行布局绘制(Paint),然后继续解析。 👉 于是肉眼看到:页面从上到下逐步展示(渐进渲染)。 浏览器不会一口气把整个 HTML 解析完再渲染;解析过程中会有微小间歇,插入渲染任务。
满足三大基础条件才能渲染: 1、已有 DOM 片段 + 对应的 CSSOM 就绪; 2、主线程空闲 3、刚好等到vsync 帧 浏览器能够调度 Layout/Paint 任务
渲染发生的时机:
JS 执行结束,HTML 解析恢复之后,主线程空闲、刚好等到vsync 帧时,才触发布局与绘制。
哪怕 DOM、CSSOM 全都准备好了,只要主线程持续被 JS、大量 HTML 解析占用,渲染就会持续延后。
同步 JS 下载、执行完成 ≠ 立刻渲染;能否马上渲染,要看两个条件:
- 当前位置之前所有阻塞渲染的 CSS 是否已经下载并解析完成(CSSOM 就绪);
- 浏览器主线程是否有空,有没有调度绘制任务。
结合完整流程,把时序拆清楚,先看标准场景: html 预览
<p>内容1</p>
<link rel="stylesheet" href="style.css">
<script src="sync.js"></script>
<p>内容2</p>场景 1:CSS 还没下载完成(最常见) 时序步骤: HTML 解析 → 生成<p>内容1DOM,遇到<link>,发起 CSS 请求;HTML 继续向下解析 解析碰到同步<script> ✅ HTML 解析器暂停,不再处理后续<p>内容2 JS 开始下载,就算 JS 先下载完毕,也不会执行! 浏览器强制等待前面的 CSS 下载、解析生成 CSSOM CSS 就绪 → JS 开始编译、执行 ✅ JS 执行完毕 → 恢复 HTML 解析 此时:虽然前置 DOM 已经存在,但渲染管线之前被 CSS 锁住; 浏览器不一定马上绘制,会等主线程空闲,批量执行布局与绘制 👉 关键点: JS 先下完、CSS 还在路上 → JS 只能等待,此时不会触发任何渲染。
场景 2:CSS 早已加载完成 html
<p>内容1</p>
<script src="sync.js"></script>
<p>内容2</p>解析生成<p>内容1DOM;遇到同步 script,暂停 HTML 解析 JS 下载完成 → 主线程编译并执行 JS JS 执行结束: HTML 解析恢复,继续解析后面<p>内容2 主线程空闲后,才会安排 Layout、Paint 渲染已有的 DOM ⚠️ 不是 JS 执行完一瞬间立刻渲染 渲染是浏览器调度的任务队列任务,主线程优先恢复 HTML 解析,绘制会延后一点执行。 最重要一条规则:同步 JS 执行期间,不会发生渲染 主线程同一时间只能干一件大事: HTML 解析 / JS 执行 / 样式重算 / 布局绘制 同步 JS 运行时: ✅ HTML 解析暂停 ✅ 渲染管线被冻结,不会触发任何新的绘制
区分两个极易混淆现象 1)JS 执行完成,满足渲染条件,什么时候出画面? 同步脚本执行完毕 → 控制权交还给 HTML 解析器 浏览器主线程任务顺序通常: 恢复 HTML 解析 → 解析一段后,主线程空闲 → 执行布局、绘制 2)为什么有时候同步 JS 跑完,页面还是空白? 两种核心原因: 前面存在未加载完成的 CSS(CSS 阻塞渲染,哪怕 JS 执行完,没有 CSSOM 依旧不能绘制) JS 执行完成后,浏览器继续解析后续 HTML,还没轮到调度 Paint 总结成 4 条核心要点 JS 下载完成 ≠ JS 立刻执行:若前面有未就绪 CSS,JS 必须等待 CSSOM;等待期间不会渲染。 JS 执行阶段,完全不会渲染页面。 JS 执行完毕:HTML 解析恢复;但渲染不会瞬时触发,需要主线程空闲,由浏览器调度绘制任务。 即便 JS 执行完毕,如果前置 CSS 仍然缺失(CSSOM 不存在),依旧无法渲染任何内容。 极简一句话背诵 同步 JS 下载好了,还要等前置 CSS 就绪才会执行 JS;JS 运行期间不会渲染;JS 执行结束后恢复 HTML 解析,等主线程空闲、且 CSSOM 齐全,才会渲染。
HTML 采用流式下载、流式解析,收到分片即解析,无需等待全文; 解析过程中预扫描器并行发现外部资源,提交网络进程调度下载; 遇到同步 script:暂停 HTML 解析、暂停渲染,等待脚本下载执行; 遇到link css:HTML 解析继续,渲染管线冻结,且阻断后方同步 JS; 在无同步脚本阻塞的前提下,只要已有 DOM 片段 + CSSOM 就绪,就可以执行布局、绘制(渐进渲染); 全部 HTML 解析完成,触发 DOMContentLoaded; 所有图片、媒体等外部资源全部加载完成,触发 load。
<p>文本1</p>
<link href="style.css" rel="stylesheet">
<p>文本2</p>
<p>文本3</p>执行流程: 浏览器边下载 HTML、边解析; 解析到 <link>:发起 CSS 请求; HTML 解析不会停止!继续向下解析,生成文本 2、文本 3 对应的 DOM 节点; 但是:渲染管线被锁住,当前已经生成的 DOM 不会绘制到屏幕; CSS 下载完成 → 解析生成 CSSOM → 解锁渲染,统一绘制页面; 如果 CSS 后面紧跟着同步 script:JS 必须等待 CSS 解析完成才能执行。 👉 重点:CSS 只阻塞渲染、阻塞后续同步 JS,不阻塞 HTML 解析。
同步 CSS:不会暂停 HTML 解析;但是会阻塞【渲染】,同时阻塞紧随它后面的同步 JS。 同步 JS:会立刻暂停 HTML 解析;同时阻塞渲染,必须等 JS 下载、解析、执行完毕,才继续解析 HTML。
浏览器是流式处理、渐进式解析、渐进式渲染; 但是存在阻塞点,遇到特定标签会暂停流程。
服务端返回 chunk 分片,浏览器收到一段就解析一段,不用等待整个 HTML 下载完毕。 HTML分片1抵达 → 解析生成部分DOM HTML分片2抵达 → 继续解析追加DOM
<link rel="stylesheet"> 不会暂停 HTML 解析
HTML 解析继续生成 DOM;只是阻塞渲染、阻塞紧随其后的同步 JS。
<p>第一段内容</p>
<script src="sync.js"></script>
<!-- 解析到这里:HTML Parser 暂停!
等待js下载 → 编译 → 执行,执行完毕,才继续往下解析HTML -->
<p>第二段内容</p>渲染不会等到 HTML 全部解析完成 在没有同步 JS 阻断的前提下: 浏览器只要满足两个条件就可以开始渲染已有 DOM: 已经解析出一部分 DOM 节点 对应 DOM 所需 CSS 已经下载、解析完成(拥有 CSSOM) 👉 页面会逐步绘制内容(渐进渲染),也就是你肉眼看到页面从上到下逐步出现。
规则 2:同步 JS 会强制打断整条链路 时序: 解析 HTML → 碰到同步 script 暂停 HTML 解析 如果 script 前面有未完成下载的 CSS,先等 CSS 下载 + 生成 CSSOM JS 下载、编译、执行 JS 执行完毕,恢复 HTML 解析 在 JS 执行期间: 无法继续解析 HTML 不会执行新的渲染 JS 可以操作 DOM、样式,浏览器必须保证执行环境稳定,不能一边解析一边渲染。
规则 3:CSS 阻塞渲染,但不阻塞 HTML 解析
HTML 继续解析,文字A、文字B都会生成 DOM 节点 但是页面暂时不会渲染这两段文字(渲染管线被锁住) CSS 下载解析完成,生成 CSSOM 后,才统一渲染
一、Chromium 多进程模型简要梳理
进程层级(独立操作系统进程,内存隔离) 浏览器主进程(Browser Process):窗口管理、导航控制 网络进程(Network Process):所有 HTTP 请求统一在这里发出 CSS、JS、img、font、video、fetch/axios、iframe 资源…… 全部网络请求都汇集到网络进程 网络进程内部是多个线程并发处理请求,不是一个请求开一个进程 渲染进程(Renderer Process):每一个标签页通常对应一个渲染进程 内部包含:渲染主线程(最重要)、工作线程、合成线程、图像解码线程等 关键点: ✅ 所有资源的网络下载,不在渲染进程,在独立网络进程。 ❌ 但是下载完成后的解析、执行绝大多数回到渲染主线程! 二、完整链路:任意资源请求流程(例:css /js/image) 渲染进程内部(预扫描器 / HTML 解析器)发现资源 URL 通过 IPC 通信,把请求转发给【网络进程】 网络进程发起 TCP 请求、接收响应、缓存处理(并发线程调度) 资源二进制数据分片通过 IPC 传回渲染进程 渲染进程接收二进制,开始解析 / 解码 / 执行 👉 网络传输:网络进程;解析运算:渲染进程内线程 三、各类资源「下载阶段」&「解析阶段」运行位置汇总
- CSS 文件 下载:网络进程 解析(构建 CSSOM):渲染主线程 CSS 解析会占用主线程,会阻塞后续同步 JS 执行
- JavaScript 文件 下载:网络进程 解析(生成 AST)+ 执行:渲染主线程 Web Worker 例外:Worker 内 JS 运行在 Worker 独立线程,不占用主线程
- 图片(jpg/webp/png) 下载:网络进程 图像解码: 默认:渲染主线程(会阻塞主线程) 设置 decoding="async":浏览器尝试放到图像解码线程(Renderer 内部子线程)
- WebFont 字体 下载:网络进程 字体解析、字形构建:渲染主线程
- 视频 / 音频 资源分片下载:网络进程 媒体解码:渲染进程内部媒体专用线程,一般不抢占主线程
- Fetch/XHR 请求 网络传输:网络进程 返回数据的回调处理:渲染主线程
所有 CSS/JS/ 图片等资源的网络下载运行在独立网络进程; 但是资源下载完成后的解析、编译、执行、图像解码,绝大多数在渲染进程的主线程执行。 网络进程只负责搬运字节,不负责解析资源内容。
整体阶段划分(URL 发起请求 → 像素展示)
导航阶段(浏览器发起 URL 请求、网络通信、服务器应答) 文档提交阶段:HTML 字节流交付渲染进程 HTML 流式解析(主线程 HTML Parser + Preload Scanner 预加载扫描器并行) 各类外部资源发现、调度、分配加载优先级、并行下载 构建 DOM、CSSOM、渲染树、布局、绘制、分层合成,页面呈现
第一部分:浏览器发起 URL 请求,服务器应答全过程
- 导航预处理(浏览器主进程) 用户输入地址 / 点击链接 → 主进程解析 URL、规范化地址、读取浏览器缓存策略、触发页面卸载事件(beforeunload) 通过 IPC 把导航任务下发网络进程
- 网络进程执行网络请求 DNS 解析:域名→IP;查询 DNS 缓存、本地 hosts、递归 DNS 服务器 连接建立:TCP (HTTP1/2) / QUIC (HTTP3);TLS 握手(HTTPS) HTTP 请求构造、发送;服务端处理业务逻辑,返回 HTTP 响应 响应报文解析:响应状态码、响应头(Cache-Control、Content-Type、Content-Encoding、CSP、Link 预加载提示等) 解压(gzip/br),分段流式返回 HTML 字节流(chunked) ✅关键特性:HTML 不需要完整下载完毕,收到第一个数据包就开始流式解析
- 文档提交 网络进程确认响应类型为text/html → IPC 通知浏览器主进程;主进程创建 / 复用渲染进程,将 HTML 字节流交付渲染进程主线程。
第二部分:渲染进程 — HTML 文档解析完整机制(核心需求 1) 标准依据:WHATWG HTML 解析算法 §13 Parsing HTML documents 两大线程协同:主线程主解析器 HTML Parser + 预加载扫描器 Preload Scanner(推测解析器 Speculative Parser)
2.1 HTML 主线程解析流水线(串行、从上到下、流式解析) 字节流 → 编码识别 → 字符流 → 词法分析(Tokenization) → 语法分析 → DOM 树 编码嗅探:依据 BOM、HTTP 响应头Content-Type、html <meta charset>确定文档编码 词法分析 (Lexer):把文本切割成 Token(开始标签、结束标签、文本、注释、属性) 语法分析 (Parser):消费 Token,生成 DOM 节点,维护节点父子层级,构建 DOM 树
标签类型 是否阻塞 HTML 解析 是否阻塞页面渲染 关键说明
<script src=""> 同步脚本(无 async/defer) ✅阻塞 ✅阻塞 防止document.write()、JS 读取 DOM / 样式造成错乱
<script async> ❌不阻塞解析(下载异步),执行瞬间阻塞 ✅执行时阻塞渲染 下载并行,下载完成立刻执行,顺序不可控
<script defer> ❌完全不阻塞解析 页面解析完成后执行 保持脚本顺序,DOMContentLoaded 前执行
<link rel="stylesheet">外联 CSS ❌不阻塞 HTML 解析 ✅渲染阻塞(必须等待 CSSOM) 允许继续解析 HTML 生成 DOM,但无法渲染页面;阻塞紧随其后的同步 JS 执行
<img>//<video>/<iframe> ❌不阻塞解析 ❌不阻塞渲染,仅资源占位CSS 不会停止 HTML 解析,只是锁住渲染管线;如果同步 JS 写在 CSS 后面,JS 必须等待 CSSOM 构建完成才能执行。
阻塞原理:同步 JS 可以调用document.write()、读取 / 修改 DOM 与样式;浏览器无法预知脚本行为,必须暂停解析执行脚本。
渲染进程存在两套解析单元并行工作:主线程 HTML 解析器 + 预加载扫描器(Preload Scanner)
2.2 Preload Scanner 预加载扫描器(浏览器核心优化机制) MDN 官方定义:独立轻量级扫描器,不构建 DOM、不执行 JS,只做简单词法扫描,提前提取外部资源 URL 发起网络请求 运行时机:与主线程 HTML Parser 并行运行;主线程被同步 script 阻塞时,预扫描器依然持续向后扫描 HTML 片段 能力:静态扫描 HTML 文本,提前提取资源 URL 发起请求,消除 “解析到标签才开始下载” 的串行等待 能识别的资源: <link rel="stylesheet"> 、<script src>、<img src>、<source>、<video>、background-image(部分现代浏览器) ❗局限性: 无法识别 JS 动态插入资源(createElement、document.write动态生成 src); 无法识别 CSS 内部引用资源(字体、背景图,必须等 CSS 下载解析后才能发现); 价值:解决串行瓶颈,不用等主线程解析走到对应标签才发起请求,提前抢占网络带宽。
3.1 Chromium 默认资源优先级总表(web.dev + Chromium 源码实测) 资源类型 默认优先级 补充说明 HTML 主文档 Highest 导航首要资源 <head>内外联 CSS(render-blocking) Highest 关键渲染路径资源 <head>内同步<script>(阻塞解析) Highest link rel="preload" 关键资源 High / Highest 由 as 属性决定 CSS 内引用 WebFont 字体 High 渲染阻塞字体 Body 内同步脚本 High 视口内图片(LCP 候选图) High 初始图片为 Low,布局完成自动升级 Body 内普通图片、音频、视频 Low 默认值 async / defer 脚本 Low 视口外图片、懒加载资源 Lowest prefetch预获取(下一页资源) Lowest 空闲带宽才下载 XHR/fetch 默认请求 High 可通过 Fetch API priority参数修改
默认资源基准优先级表
资源类型 默认优先级 备注
HTML 主文档 Highest 导航首要资源
<head>内阻塞渲染 CSS、头部同步 script Highest 关键渲染路径 CRP 资源
link rel="preload"(关键资源) High/Highest 由as属性决定档位
CSS 中加载 Web 字体 High 字体加载会阻塞文本渲染
body 内同步脚本 High
初始<img>(未布局前) Low 布局后判断是否在视口内动态升级
视口内图片(LCP 候选) High 运行时自动提升优先级
async /defer 脚本 Low
普通图片、audio、video Low
视口外图片、默认懒加载图像 Lowest
link rel="prefetch" Lowest 仅网络空闲时下载,用于下一页资源
fetch/XHR 默认请求 High 可通过fetchpriority调整fetch() API 传入 priority 参数
fetch('/data.json', { priority: 'high' })3.2 资源优先级动态调整规则(浏览器运行时自动变更) 图片优先级动态升降(最典型) 预扫描阶段识别<img> → 初始分配 Low 主线程完成布局 Layout,判断图片是否位于可视视口内 ✅视口内 → 提升至 High(LCP 图片优化) ❌视口外 → 维持 Low/Lowest 开发者干预手段:fetchpriority="high" 直接强制提升,不用等待布局计算
<!-- LCP首屏主图,手动提升优先级 -->
<img src="hero.webp" fetchpriority="high">
<!-- LCP首屏大图,强制高优先级,无需等待布局判断 -->
<img src="hero.jpg" fetchpriority="high">
<!-- 非首屏次要图片,降低优先级 -->
<img src="banner.jpg" fetchpriority="low">CSS 与脚本联动优先级约束 同步脚本如果出现在<link rel="stylesheet">之后:脚本必须等待 CSS 下载解析完成才能执行(脚本可能读取样式),间接延长阻塞时间。 预加载 link rel="preload" 强制浏览器提前发起请求,分配更高优先级;用于预扫描器无法自动发现的资源(CSS 字体、动态 JS)
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>3.3 影响资源优先级的全部因素(按权重排序) 资源类型 + 在 HTML 中出现位置(默认基准优先级) 是否属于关键渲染路径(是否阻塞渲染 / HTML 解析) Preload Scanner 何时发现该资源 浏览器运行时判断:图片是否在视口内 开发者主动信号:fetchpriority、rel="preload"、link header preload 网络空闲状态、连接数量、HTTP 版本 3.4 不同浏览器实现差异说明(多渠道数据不一致重点标注) Chromium(Chrome/Edge):明确 5 档优先级,Network 面板可直接查看 Priority 列;支持fetchpriority;业界文档最多。 Firefox(Gecko):优先级档位内部命名不同,不对外暴露完整档位;对图片自动提级逻辑与 Chrome 类似,但调度权重算法不一致。 Safari(WebKit):早期版本不支持fetchpriority,新版逐步兼容;字体、图片优先级策略偏保守。 冲突原因:WHATWG 标准仅定义资源提示语义,未强制规定优先级数值档位,属于浏览器厂商自主实现范围,因此无法做到完全统一。
第四部分:资源加载完成后,后续渲染链路(串联完整流程)
全部资源持续并行下载;主线程持续构建 DOM; CSS 文件下载完成 → 解析生成 CSSOM 树; DOM + CSSOM 合并生成渲染树 Render Tree(排除display:none不可见节点); 布局 Layout (Reflow):计算渲染树每个元素坐标、尺寸; 绘制 Paint:填充颜色、图片、文字、阴影,生成绘制指令; 分层、栅格化、合成 Composite,交由 GPU 输出像素到屏幕。
第五部分:高频误区澄清(基于官方规范纠正网传错误结论)
❌误区:浏览器必须等 HTML 全部下载完才开始解析 ✅正解:流式解析,收到第一个 TCP 数据包即可解析。浏览器收到 HTML 流式字节流,不需要完整下载全部 HTML,边下载、边解析(流式解析)
❌误区:预加载扫描器可以识别 CSS 里的背景图、字体 ✅正解:不能;必须等 CSS 文件下载并解析后才能发现。
❌误区:async/defer 脚本不会占用网络带宽、高优先级 ✅正解:默认优先级 Low,带宽竞争弱于同步脚本、CSS。
❌误区:<link rel="stylesheet">阻塞 HTML 解析 ✅正解:不阻塞 HTML 解析,只阻塞渲染;但会阻塞后续同步脚本执行(MDN 明确结论)。
❌ 图片加载阻塞页面解析 ✅ 图片下载完全不阻塞 HTML 解析与 DOM 构建
- 主线程 HTML 主解析器(HTML Parser) 执行标准 HTML 解析算法,流程: 字节流 → 编码检测 → 字符流 → 分词(Token)→ 构建 DOM 树
打开 Chrome DevTools → Network 面板,右键表头勾选 Priority,实时观察浏览器分配的资源优先级; 可对比:普通图片 / LCP 图片 /preload 资源 /async 脚本 的优先级差异,验证上述规则。
三、HTML 中各类外部资源:下载时机、解析时机、约束关系
区分两个概念:
下载:网络层面获取资源二进制;
解析:浏览器收到完整资源后,解码、生成对应数据结构(CSSOM、图像位图、脚本 AST 等)
1. 外部 CSS <link rel="stylesheet" href="xxx.css">
发现时机:主线程解析遇到标签 / Preload Scanner 提前捕获
下载:异步发起网络请求
解析时机:CSS 完整下载完毕后,主线程解析 CSS,生成 CSSOM(CSS 对象模型)
约束:
阻塞页面渲染;
位于 CSS 之后的同步脚本,必须等待 CSSOM 就绪才能执行。
2. JavaScript 外部脚本
① 同步脚本 <script src="main.js">
发现:预扫描器提前发起下载
下载:并行;但下载完成后必须等待主线程空闲
解析 & 执行:主线程暂停 HTML 解析,读取 JS、编译生成 AST、执行代码
执行完毕后,HTML 解析继续
② async <script async src>
下载:异步并行,不阻塞 HTML 解析
解析执行:下载完成立刻抢占主线程执行,随时打断渲染,执行顺序混乱
③ defer <script defer src>
下载:异步并行
解析执行:HTML 全部解析完成后,DOMContentLoaded 之前按顺序执行,全程不阻塞解析
3. 图片 <img src>、picture、source
发现:预扫描器捕获,发起下载
下载:并行,不阻塞 HTML 解析、不阻塞渲染(图片缺失位置显示空白占位)
解析时机:图片二进制下载完成后进行解码(图像解码,生成位图)
现代浏览器优化:图像解码可放到合成线程,不占用主线程;超大图解码仍可能阻塞主线程
特点:图片下载失败不会中断页面解析与渲染
4. 字体资源(woff2 等)
发现时机:预扫描器无法发现!
字体定义写在 CSS 内部,必须等待 CSS 下载、CSS 解析完成后,浏览器才知道字体 URL,再发起请求
下载:并行
渲染规则:默认存在字体闪烁机制(FOIT/FOUT),字体未加载完成前文字可能暂时不渲染
5. 音频 ``、视频 <video>
预扫描器识别 src,发起请求;
默认不会完整下载全部媒体文件,浏览器只预加载一小部分缓冲区;
视频解码开销大,媒体资源加载优先级普遍偏低;
增加preload="none"可以关闭自动预加载。
6. iframe
预扫描器识别;
相当于嵌套一次完整导航:重新执行一套 HTML 解析、资源加载流程;
资源开销很高,优先级通常低于首屏关键资源。
7. preload /prefetch 资源
<link rel="preload" href="font.woff2" as="font" crossorigin>
preload:立刻高优先级下载,弥补预扫描器盲区(比如 CSS 字体),页面当前渲染需要;
prefetch:低优先级,浏览器空闲时下载,用于下一个页面资源。
四、资源优先级调度简要规则(Chromium 标准)
优先级从高到低:Highest > High > Medium > Low > Lowest
HTML 主文档、头部阻塞 CSS、头部同步脚本:Highest
首屏 LCP 图片、Web 字体、preload 关键资源:High
普通图片、async/defer 脚本、音视频:Low
视口外图片、prefetch:Lowest
动态变化:图片初始 Low,布局完成后判断处于可视区域,自动升级为 High;
开发者干预:<img fetchpriority="high">
五、DOM、CSSOM → 渲染链路(资源全部解析后)
DOM 树 + CSSOM 树 → 渲染树 Render Tree(排除display:none节点)
Layout(布局 / 回流):计算每个元素坐标、尺寸
Paint(绘制):填充颜色、图片、文字
Composite(分层合成),交由 GPU 输出画面
六、完整极简时序示例
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="sync.js"></script>
</head>
<body>
<img src="lcp.jpg">
</body>
</html>
HTML 流式抵达,主解析器开始解析;预扫描器同步向后扫描
预扫描器同时发现 css、sync.js、img,并行发起请求
css 优先级更高优先下载;sync.js 需要等待 css 解析完成才能执行
主线程遇到 sync 脚本,暂停 HTML 解析,等待脚本下载、执行
脚本执行结束,恢复 HTML 解析
img 下载完成后解码;布局完成识别为首屏图片,提升加载优先级
HTML 全部解析完成,生成完整 DOM;CSS 就绪生成 CSSOM
渲染树、布局、绘制,页面展示哪些资源加载 / 解析 / 执行会阻塞浏览器主线程
浏览器渲染主线程同时负责:HTML 解析、DOM 构建、CSS 解析、布局 Layout、绘制 Paint、JS 执行、部分图像解码。 一旦主线程被阻塞,上述所有工作全部暂停。 区分两层极易混淆的概念: 阻塞 HTML 解析:停止 HTML Parser 生成 DOM 阻塞渲染管线:DOM 可以继续构建,但页面无法绘制 阻塞主线程:主线程被占用,所有任务排队等待 一、会直接阻塞主线程的场景分类
- JavaScript(影响最大) ① 普通同步脚本
<script src="xxx.js">行为: HTML 解析遇到标签 → 暂停 HTML 解析(阻塞 Parser) JS 下载完成后,主线程执行:解析 JS(生成 AST)+ 执行代码 ✅ 全程占用主线程,阻塞主线程 原因:JS 可读写 DOM、执行document.write(),浏览器无法预判代码行为。 ② 内联同步<script> //code </script>无需网络下载,直接在主线程解析并执行,阻塞 HTML 解析与主线程 ③ async 脚本 下载阶段不阻塞解析、不占主线程; 脚本下载完成后,立刻抢占主线程解析 + 执行 👉 执行阶段阻塞主线程,只是阻塞时机不确定。 ④ defer 脚本 下载完全并行; HTML 全部解析完成后,主线程集中执行 👉 执行瞬间阻塞主线程(只是推迟到解析结束)。 ⑤ 动态创建脚本 document.createElement('script') 默认表现类似 async:下载完成后立即执行,执行阶段阻塞主线程。 ✅总结:任何 JS 代码最终执行阶段,一定会占用主线程。 - CSS 样式表
<link rel="stylesheet">重点澄清高频误区: ❌ 不会阻塞 HTML 解析(HTML Parser 继续往下生成 DOM) ✅ 阻塞渲染管线 ✅ 阻塞紧随其后的同步 JS 执行 机制: 同步 JS 可以读取元素样式(getComputedStyle、offsetWidth), 如果 JS 写在 CSS 后面,浏览器必须等待 CSS 解析生成 CSSOM,才能运行 JS。 => 间接拉长主线程阻塞时间。 CSS 本身的解析工作(构建 CSSOM)运行在主线程。 - 图像解码(图片、webp、png、jpg) 两种情况: 大图 / 未优化图像:下载完成后,图像解码在主线程执行 → 阻塞主线程 现代浏览器优化:支持离线解码(解码线程),但并非所有场景都生效 解决方案:
<img decoding="async" src="xxx.jpg">告诉浏览器尝试放到异步线程解码,减少主线程阻塞。 ⚠️ 重要:图片下载本身不阻塞 HTML 解析、不阻塞渲染;只有「图像解码」有可能阻塞主线程。 4. Web 字体(@font-face) 字体文件下载完成后,字体解析、字形构建运行在主线程。 同时触发 FOIT/FOUT 文字渲染阻塞策略。 字体资源还有一个特点:预扫描器识别不到,必须 CSS 解析完才发起请求。 5. HTML 解析本身(主线程任务) HTML Token 分词、DOM 节点创建,全部运行在主线程; 当 HTML 体量极大时,长时间解析会占用主线程。 二、【不会阻塞主线程】的资源(区分清楚) 图片、音视频的网络下载过程(下载是网络进程,不占用主线程) ⚠️只是下载不阻塞;解码有可能阻塞主线程 async/defer 脚本网络下载阶段 preload/prefetch 资源的下载 iframe 内部资源下载(独立渲染进程) Web Worker 内部脚本(Worker 拥有独立线程,不阻塞主线程)
资源 / 行为 是否阻塞 HTML 解析 是否阻塞渲染 是否占用主线程 关键点
同步外联 script ✅是 ✅是 ✅是 阻塞源头之首
内联 script ✅是 ✅是 ✅是 无网络等待,直接阻塞
async 脚本(下载阶段) ❌ ❌ ❌ 下载不阻塞
async 脚本(执行阶段) ❌ ✅ ✅ 下载完立刻抢占主线程
defer 脚本(下载阶段) ❌ ❌ ❌
defer 脚本(执行阶段) ❌ ✅ ✅ DOM 解析完毕后执行
<link rel="stylesheet"> ❌ ✅ ✅(CSSOM 构建) 阻塞后方同步 JS
图片【网络下载】 ❌ ❌ ❌
图片【图像解码】 ❌ ❌ ⚠️可能阻塞 使用 decoding="async" 优化
Web 字体下载 + 解析 ❌ ⚠️文字渲染阻塞 ✅解析占用主线程
HTML 超大文档解析 ✅持续占用主线程 — ✅渲染进程 ↔ 网络进程 的资源请求通信链路
阶段 运行位置 是否跨进程 重点说明
资源网络请求、下载、缓存读取 网络进程 跨进程 IPC CSS/JS/ 图片 / 字体全部统一在这里调度,存在带宽、连接竞争
HTML 解析、DOM 构建 渲染进程主线程 同进程 主线程任务
CSS 解析生成 CSSOM 渲染进程主线程 同进程 占用主线程
JS 编译、执行(非 Worker) 渲染进程主线程 同进程 最大阻塞源
图片解码(默认) 渲染进程主线程 同进程 可通过decoding="async"分流至子线程
音视频媒体解码 渲染进程媒体子线程 同进程 一般不阻塞主线程四、生产环境优化方案(针对主线程阻塞) 同步 JS 尽量移到文档末尾,或者改为 defer /async 首屏关键 JS 内联,非关键 JS 延迟加载 CSS 尽可能精简,优先加载关键 CSS,减少阻塞同步脚本 图片加上 decoding="async",避免大图解码阻塞主线程 复杂计算放入 Web Worker 避免在首屏大量使用巨大图片、大量自定义字体 使用 fetchpriority 合理调度资源,减少主线程资源争抢
渲染引擎的基本流程
- 渲染引擎将开始解析 HTML 文档并将元素转换为名为“内容树”的树中的DOM节点。引擎将解析外部 CSS 文件和样式元素中的样式数据。HTML 中的样式信息和视觉指令将用于创建另一棵树:渲染树。
- 渲染树包含具有颜色和尺寸等视觉属性的矩形。
- 在构建渲染树之后,它会经历一个“布局”过程。这意味着为每个节点提供它应该出现在屏幕上的确切坐标。下一阶段是绘制——将遍历渲染树,并使用 UI 后端层绘制每个节点。
- 在开始构建和布局渲染树之前,它不会等到所有 HTML 都被解析。部分内容将被解析和显示,而该过程将继续处理来自网络的其余内容。

WebKit 渲染引擎主流程

Gecko 渲染引擎主流程

像素到屏幕的常规渲染管道
其他两种特殊渲染管道
渲染流水线
浏览器用于将 HTML、CSS 和 JavaScript 转换为像素的算法统称为渲染流水线。
- 降低绘制的复杂性并减少绘制区域
- 如果您触发布局,则始终会触发绘制,因为更改任何元素的几何图形意味着其像素需要修复,如下图所示:

- 如果您更改非几何图形属性(例如背景、文本颜色或阴影),也可以触发绘制。在这些情况下,不需要布局,并且流水线将如下所示:

web文档资源加载执行时机
HTML文档
HTML文档是浏览器发起网络请求后等待服务器响应而优先下载解析的文档,它是构建DOM的前提。
- 加载机制:发起URL请求后就开始进行网络请求与加载HTML文档。
- 执行时机:当下载完成HTML文档后就开始解析HTML文档。
readystatechange
关于DOMContentLoaded,不得不提readystatechange,通过document.readyState值来更进一步来判断文档状态:
- uninitiated:xml 对象被产生,但没有任何文件被加载。
- loading:document正在下载,文件尚未开始解析。
- loaded:部分的文件已经加载且进行解析,但对象模型尚未生效。
- interactive:document完成了解析,但是资源还在下载,对象模型是有效但只读的。
- complete:代表加载成功,文档加载完成,并且所有resource都加载完毕
style样式资源
style样式资源,即以
<link rel="stylesheet" href="">为外链发起的网络请求资源。
- 加载机制:解析器解析HTML文档的过程中,如果遇到style样式资源网络请求标识,浏览器会发起请求这些资源,而不影响解析器的解析工作。等待获取style样式资源会阻塞 JavaScript的执行。
- 执行时机:下载完成后,直接开始构建CSS OM树。
javascript脚本资源
javascript脚本资源,即以
<script type="text/javascript" src=""></script>为外链发起的网络请求资源。
- 加载机制:解析器解析HTML文档的过程中,如果遇到style样式资源网络请求标识,浏览器会发起请求这些资源,但是会阻塞渲染并停止 HTML 的解析。
- 执行时机:等待HTML文档解析完成后,以及CSS资源加载与执行完成后,开始解析JS。
defer属性
具体见
HTML5 生态体系 script标签。
async属性
具体见
HTML5 生态体系 script标签。
图片资源
图片资源,即以
<img src="" alt="" title="" />为外链或脚本图片对象发起的网络请求资源。
- 加载机制:滞后于样式资源与脚本资源,解析器解析HTML文档的过程中,如果遇到图片资源网络请求标识,浏览器会发起请求这些资源,而不影响解析器的解析工作。
- 执行时机:等待HTML文档首次绘制后,下载完成即可执行。
多媒体资源
多媒体资源,即以语音、视频等对象在网页中发起的网络请求资源。
- 加载机制:滞后于样式资源与脚本资源,解析器解析HTML文档的过程中,如果遇到多媒体资源网络请求标识,浏览器会发起请求这些资源,而不影响解析器的解析工作。
- 执行时机:等待HTML文档首次绘制后,下载完成即可执行。
其他资源
除了以上列举等对象外的其他在网页中发起的网络请求资源。
- 加载机制:滞后于样式资源与脚本资源,解析器解析HTML文档的过程中,如果遇到其他资源网络请求标识,浏览器会发起请求这些资源,而不影响解析器的解析工作。
- 执行时机:等待HTML文档首次绘制后,下载完成即可执行。
网页URL生命周期
DNS解析阶段
- 应用端系统DNS解析阶段 | 发起URL请求
步骤一:检索本地浏览器DNS缓存
步骤二:检索本地操作系统DNS缓存
步骤三:检索Host文件
步骤四:查询是否存在配置指定DNS服务器
步骤五:从路由器DNS缓存查询
- 应用端DNS检索无效,进入DNS服务商解析阶段
步骤一:从DNS缓存系统查询:DNS记录
步骤二:从域名数据库查询,域名-IP映射表
步骤三:递归查询:在某个DNS服务器上查找不到相应的域名与IP地址对应关系时,自动转到另一台DNS服务器上进行查询。
- 与应用服务器建立连接通信
流程
布局 绘制
JS引擎
浏览器渲染方式:软件渲染(cpu)、硬件渲染(GPU)
cpu渲染:浏览器发生布局,绘制阶段;如果renderObject更新改变,相关联的renderObject发生改变而不是整个layer的Object发生改变,成为cpu缓存。
GPU渲染:发生组合阶段,如果页面某个合成层发生位置的偏移、缩放、透明度等操作,GPU会把更新的合成层进行相应的变化送入compositor(合成器)重新合成即可。
减少布局和绘制的措施
在重新渲染触发硬件加速而跳过重排和重绘的过程
dom tree -> render tree(渲染树,只显示display: 非none) -> layout tree (布局树,经过盒子模型的树) -> paint -> composite layer
浏览器处理HTML机制
递增处理: HTML 文档由解析器递增解析,从而保证文档可以间隙性发送
浏览器处理javascript
等待整个文件下载OK
浏览器处理CSS
等待整个文件下载OK
CSS 文件越大,浏览器在构建 CSSOM 前经历的阻塞时间就越长,从而推迟首次绘制页面的时间。类似地,JavaScript 文件越大,对执行速度的影响同样越大;小文件倒是能实现“递增式”执行。
网页工作的实现原理

![]() | ![]() |
| 自下而上 | 调用树 |
waterfall资源瀑布结构
- Queueing:排队,浏览器在连接开始之前以及以下情况下对请求进行排队
- 有更高优先级的请求
- 此源已打开六个 TCP 连接,这是限制。仅适用于 HTTP/1.0 和 HTTP/1.1。
- 浏览器正在短暂地分配磁盘缓存中的空间。
- Stalled:停滞不前,连接启动后,由于排队中所述的任何原因,请求可能会停止。
- DNS Lookup:DNS查找,浏览器正在解析请求的 IP 地址。
- initial Connection:初始连接,浏览器正在建立连接,包括 TCP 握手/重试和协商 SSL。
- Proxy negotiation:代理协商,浏览器正在与代理服务器协商请求。
- Request Sent:正在发送请求。
- ServiceWorker Preparation:浏览器正在启动 Service Worker。
- SSL Negotiation:
- Request to ServiceWorker:请求正在发送给 Service Worker。
- Waiting (TTFB):浏览器正在等待响应的第一个字节。TTFB 代表 第一个字节的时间。此计时包括 1 次往返延迟和服务器准备响应所花费的时间。
- Time to First Byte(TTFB): 第一个字节返回时间。
- Content Download:浏览器正在接收响应,无论是直接从网络还是从 Service Worker 接收。此值是读取响应正文所花费的总时间。大于预期值可能表示网络速度较慢,或者浏览器正忙于执行其他工作,从而延迟读取响应。
- Bytes Downloaded:
- Response:
- Start Render:
- DOM Content Loaded:
- Content Load:
- On Load:
- Document Complete:
- Receiving Push:浏览器正在通过 HTTP/2 服务器推送接收此响应的数据。
- Reading Push:浏览器正在读取之前接收的本地数据。
通过减少页面的重新计算
布局"和绘画"的数量来提高页面性能
网页响应
一旦我们建立了到 web 服务器的连接,浏览器就代表用户发送一个初始的 HTTP GET 请求,对于网站来说,这个请求通常是一个 HTML 文件。一旦服务器收到请求,它将使用相关的响应头和 HTML 的内容进行回复。
注意事项
网页响应的第一个响应数据包需要遵守TCP 慢启动 / 14KB 规则,保障第一个数据包在14K内,包含HTML文档的首次事件循环的渲染绘制内容。
解析
解析是浏览器将通过网络接收的数据转换为 DOM 和 CSSOM 的步骤,通过渲染器把 DOM 和 CSSOM 在屏幕上绘制成页面。
- 词法分析:将输入分解为标记的过程。
- 句法分析:语言语法规则的应用。
CSSOM解析过程

DOM解析过程
仅仅呈现display不为none的节点

解析器类型
- 自顶向下解析器:检查语法的高级结构并尝试找到规则匹配。
- 自底向上解析器:从输入开始,逐渐将其转换为语法规则,从低级规则开始,直到满足高级规则。
解析器工作机制
- 解析器发现非阻塞资源,例如一张图片,浏览器会请求这些资源并且继续解析。
- 当遇到一个 CSS 文件时,解析也可以继续进行。
- 当遇到
<script>标签(特别是没有 async 或者 defer 属性的)会阻塞渲染并停止 HTML 的解析。 - 等待获取 CSS 不会阻塞 HTML 的解析或者下载,但是它确实会阻塞 JavaScript,因为 JavaScript 经常用于查询元素的 CSS 属性。
预加载扫描器
预加载扫描器,即辅助解析器,独立线程,它会遍历 HTML 源代码的其余部分以查找更多要加载的资源。
- 浏览器构建 DOM 树时,这个过程占用了主线程。
- 当这种情况发生时,预加载扫描仪将解析可用的内容并请求高优先级资源,如 CSS、JavaScript 和 web 字体。
- 它将在后台检索资源,以便在主 HTML 解析器到达请求的资源时,它们可能已经在运行,或者已经被下载。
- 预加载扫描仪提供的优化减少了阻塞,即不必等到解析器找到对外部资源的引用来请求它。
网页解析的五个步骤
- 构建 DOM 树:处理 HTML 标记并构造 DOM 树。

- 构建 CSSOM 树:处理 CSS 并构建 CSSOM 树。浏览器将 CSS 规则转换为可以理解和使用的样式映射。浏览器遍历 CSS 中的每个规则集,根据 CSS 选择器创建具有父、子和兄弟关系的节点树。
- 构建渲染树:将 DOM 和 CSSOM 组合成一个 Render 树,计算样式树或渲染树从 DOM 树的根开始构建,遍历每个可见节点。
- 布局Layout:在渲染树上运行布局以计算每个节点的几何体。
- 绘制:将各个节点绘制到屏幕上,第一次出现的节点称为 first meaningful paint。
- 在绘制或光栅化阶段,浏览器将在布局阶段计算的每个框转换为屏幕上的实际像素。
- 绘画包括将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)。
- 为了确保平滑滚动和动画,占据主线程的所有内容,包括计算样式,以及回流和绘制,必须让浏览器在 16.67 毫秒内完成。
- 分层:绘制可以将布局树中的元素分解为多个层。将内容提升到 GPU 上的层(而不是 CPU 上的主线程)可以提高绘制和重新绘制性能。
- 合成:当文档的各个部分以不同的层绘制,相互重叠时,必须进行合成,以确保它们以正确的顺序绘制到屏幕上,并正确显示内容。
编译
JavaScript脚本被解析为抽象语法树,JS引擎使用抽象语法树并将其传递到解释器中,输出在主线程上执行的字节码。

编译流程
构建辅助功能树
- 浏览器还构建辅助设备用于分析和解释内容的辅助功能(accessibility)树。
- 当 DOM 更新时,浏览器会更新辅助功能树。
处理脚本和样式表的顺序
JS脚本
在解析器到达
<script>标记时立即解析和执行脚本。文档的解析会暂停,直到脚本执行完毕。如果脚本是外部的,则必须首先从网络中获取资源——这也是同步完成的,并且解析会暂停,直到获取资源。
defer属性改变JS脚本执行机制,即不会停止文档解析,而是在文档解析后执行。
推测解析
在执行脚本时,另一个线程解析文档的其余部分并找出需要从网络加载的其他资源并加载它们。通过这种方式,可以在并行连接上加载资源并提高整体速度。注意:推测解析器只解析对外部资源的引用,如外部脚本、样式表和图像:它不会修改 DOM 树——它留给主解析器。
CSS样式表
当样式表仍在加载和解析时,Firefox 会阻止所有脚本。WebKit 仅在脚本尝试访问可能受卸载样式表影响的某些样式属性时才阻止脚本。
布局 Layout
当渲染器被创建并添加到树中时,它没有位置和大小,计算这些值称为布局或回流。
布局是一个递归过程。它从根渲染器开始,它对应于<html>HTML 文档的元素。布局通过部分或全部帧层次递归地继续,为每个需要它的渲染器计算几何信息。
脏位系统
为了不为每一个小的变化做一个完整的布局,浏览器使用一个“脏位”系统。更改或添加的渲染器将其自身及其子级标记为“脏”:即需要布局。
全局布局
布局可以在整个渲染树上触发的行为。
- 触发时机
- 影响所有渲染器的全局样式更改,例如字体大小更改。
- 由于屏幕被调整大小。
增量布局
只有脏渲染器会被布局(这可能会导致一些损坏,需要额外的布局)。
- 触发时机
- 当渲染器变脏时(异步)触发增量布局:例如,当额外的内容来自网络并被添加到 DOM 树后,新的渲染器被附加到渲染树。
- 要求样式信息的脚本,例如调度offsetHeight。
异步布局
同步布局
缓存机制
当布局由“调整大小”或渲染器位置(而不是大小)的更改触发时,渲染大小将从缓存中获取并且不会重新计算。
Painting 绘画
在绘画阶段,会遍历渲染树,调用渲染器的“paint()”方法在屏幕上显示内容。绘画使用 UI 基础结构组件。
全局绘制
整棵树被绘制
增量绘制
树的部分被绘制
绘画顺序
绘画顺序,即元素在堆叠上下文中的堆叠顺序。
根元素的背景只在整个画布上绘制一次。

块渲染器的堆叠顺序
- 背景颜色
- 背景图片
- 边界
- 子级及其子孙级
- 大纲
动态变化
浏览器会尝试执行尽可能少的操作以响应更改。
- 对元素颜色的更改只会导致元素重绘。
- 对元素位置的更改将导致元素、其子元素和可能的兄弟元素的布局和重绘。
- 添加 DOM 节点将导致节点的布局和重绘。
- 重大更改,例如增加“html”元素的字体大小,将导致缓存失效、重新布局和重新绘制整个树。
构建DOM的基本过程
- 通过网络获取字节流和字符;
- 对字符序列进行分词操作,得到一个个token;
- 根据token序列分析语法,得到一个个节点node;
- 根据node序列,分析并构建DOM树。

构建CSSOM的基本过程
CSS 对象模型 (CSSOM) 是树形形式的所有 CSS 选择器和每个选择器的相关属性的映射,具有树的根节点,同级,后代,子级和其他关系。

构造渲染树的基本过程
等到 DOM 和 CSSOM 完成之后,浏览器构造渲染树,计算所有可见内容的样式,浏览器依次使用渲染树来布局和绘制网页。
composite layer:合成图层
CSS3的transform、transition、animate之所以性能好,是因为不经过Layout与Painting阶段,直接执行composite layer
CPU向GPU传输位图
解析CSS算法
样式计算
解析HTML算法
标记化算法
树构建算法
标记化和树构建 标记化是词法分析过程,将输入内容解析成多个标记。HTML 标记包括起始标记、结束标记、属性名称和属性值。 标记生成器识别标记,传递给树构造器,然后接受下一个字符以识别下一个标记;如此反复直到输入的结束。
渲染机制
渲染管道
浏览器用于将 HTML、CSS 和 JavaScript 转换为像素的算法统称为渲染管道。
渲染
渲染步骤包括样式、布局、绘制,在某些情况下还包括合成。在解析步骤中创建的 CSSOM 树和 DOM 树组合成一个 Render 树,然后用于计算每个可见元素的布局,然后将其绘制到屏幕上。
- Render Tree:渲染树
会呈现所有的节点(包括display的各种状态值)
- 样式Style
- 将 DOM 和 CSSOM 组合成一个 Render 树,计算样式树或渲染树从 DOM 树的根开始构建,遍历每个可见节点。
- 布局Layout
- 在渲染树上运行布局以计算每个节点的几何体。布局是确定呈现树中所有节点的宽度、高度和位置,以及确定页面上每个对象的大小和位置的过程。回流是对页面的任何部分或整个文档的任何后续大小和位置的确定。
- 构建渲染树后,开始布局。渲染树标识显示哪些节点(即使不可见)及其计算样式,但不标识每个节点的尺寸或位置。为了确定每个对象的确切大小和位置,浏览器从渲染树的根开始遍历它。
绘制Paint
把像素刷到屏幕,
将各个节点绘制到屏幕上,第一次出现的节点称为 first meaningful paint
渲染器
Firefox 将渲染树中的元素称为“帧”, WebKit 使用术语渲染器或渲染对象。
关键渲染路径CRP
CRP(Critical render path),即关键渲染路径,是浏览器将 HTML,CSS 和 JavaScript 转换为屏幕上的像素所经历的步骤序列,包含了 文档对象模型 (DOM),CSS 对象模型 (CSSOM),渲染树和布局。
重要通知
关键渲染路径包括浏览器显示第一屏内容所需的所有资源,了解和优化关键渲染路径对于确保重排和重绘可以每秒 60 帧的速度进行,以确保高效的用户交互并避免讨厌是很重要的。优化关键渲染路径可以缩短首次渲染的时间,从而提高渲染性能。
影响的相关性能指标
- First Contentful Paint (FCP): 首次内容绘制时间
- Largest Contentful Paint(LCP): 最大内容绘制
关键请求链
关键请求链是对页面呈现非常重要的一系列相关网络请求。链的长度越长且下载大小越大,对网页加载性能的影响就越显著。
降低关键请求链对性能的影响
使用关键请求链审核结果先确定对网页加载影响最大的资源。
- 尽可能减少关键资源的数量:消除关键资源、延迟下载、将其标记为 async 等等。
- 优化关键字节数以缩短下载时间(往返次数)。
- 优化其余关键资源的加载顺序:尽早下载所有关键资源,以缩短关键路径长度。
文档对象模型 (DOM)
HTML 响应变成令牌(token),令牌变成节点,而节点又变成 DOM 树。 单个 DOM 节点以 startTag 令牌开始,以 endTag 令牌结束。 节点包含有关 HTML 元素的所有相关信息。
- DOM 构建是增量。
- DOM 包含页面所有的内容。
注意事项
节点数量越多,关键渲染路径中的后续事件将花费的时间就越长。
CSS 对象模型 (CSSOM)
- CSSOM 构建是非增量。
- CSSOM 包含了页面所有的样式。
- CSS 渲染阻塞是因为规则可以被覆盖。
注意事项
越是具体的标签浏览器就需要更多的工作,但这样的弊端未必值得优化。在 CSS 中选择器的性能优化,提升仅仅是毫秒级。
渲染树
渲染树包括了内容和样式:DOM 和 CSSOM 树结合为渲染树。
- 渲染树只包含了可见内容。
- 如果有元素上有 display: none;,它本身和其后代都不会出现在渲染树中。
布局
渲染树被构建完成后,开始布局。
- 布局取决于屏幕的尺寸。
- 节点数越多,布局就需要更长的时间。
- 滚动或者其他动画将会导致迟滞。
注意事项
为了减小布局事件的频率和时长,批量更新或者避免改动盒模型属性。
重排、重绘与合成
部分CSS属性成本
在渲染瀑布的上下文中,一些CSS属性在重排、重绘、合成与绘制之间的损耗成本各不相同。

重排(回流) Reflow
重排是在网络浏览器中执行的一个流程,用于重新计算文档中各元素的位置和几何形状,以便重新呈现该文档的部分内容或全部内容。
重排动机
- 添加或删除可见的DOM元素;
- 元素位置的改变;
- 元素尺寸的改变(padding、margin、border、height和width);
- 内容改变,例如文本改变或图片被另一个不同尺寸的图片替代;
- 页面渲染器初始化;
- 浏览器窗口尺寸改变。
浏览器通过队列化修改并批量执行来优化重排过程,其中获取布局信息会导致队列刷新
- offsetTop, offsetLeft, offsetWidth, offsetHeight
- scrollTop, scrollLeft, scrollWidth, scrollHeight
- clientTop, clientLeft, clientWidth, clientHeigh
- getComputedStyle(), currentStyle、getBoundingClientRect()
建议准则
- 减少不必要的 DOM 深度。在 DOM 树中的一个级别进行更改可能会致使该树的所有级别(上至根节点,下至所修改节点的子级)都随之变化。这会导致花费更多的时间来执行重排。
- 尽可能减少 CSS 规则的数量,并移除未使用的 CSS 规则。
- 如果您想进行复杂的渲染更改(例如动画),请在流程外执行此操作。您可以使用 position-absolute 或 position-fixed 来实现此目的。
- 避免使用不必要且复杂的 CSS 选择器(尤其是后代选择器),因为此类选择器需要耗用更多的 CPU 处理能力来执行选择器匹配。
重绘 Repaint
RepaintAfterLayout (RAL),即布局后重绘,是在不改变文档布局的情况下,当某些东西以前不可见时变为可见时发生的情况。
重绘动机
- 触发重排动机,就会引起重绘行为。
建议准则
- 尽量减少触发重绘的操作行为。
合成
合成是将页面上绘制的部分组合在一起以在屏幕上显示的过程。
注意事项
分层确实可以提高性能,但是它以内存管理为代价,因此不应作为 web 性能优化策略的一部分过度使用。
- 当文档的各个部分以不同的层绘制,相互重叠时,必须进行合成,以确保它们以正确的顺序绘制到屏幕上,并正确显示内容。
- 未合成的动画需要进行更多工作,并且在低端手机上或在主线程上运行性能密集型任务时,可能会出现卡顿(不流畅)问题。
合成动机
- 分层将内容提升到 GPU 上的层(而不是 CPU 上的主线程)可以提高绘制和重新绘制性能。
- 有一些特定的属性和元素可以实例化一个层,包括
<video>和<canvas>,任何 CSS 属性为 opacity 、3D transform、will-change 的元素,还有一些其他元素。 - 这些节点将与子节点一起绘制到它们自己的层上,除非子节点由于上述一个(或多个)原因需要自己的层。
建议准则
- 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
- 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
- 对于 transform 和 opacity 效果,不会触发 layout 和 paint
未合成的动画
未合成的动画在低端手机上或在主线程上运行性能较高的任务时可能会出现卡顿(不流畅)。动画卡顿会增加网页的 Cumulative Layout Shift (CLS)。
绘图与绘画的区别
绘图
绘图是将图层组合成最终屏幕图像的合成器。
绘画
绘画包括将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)。
网络字体资源
每个字体文件的传输延迟取决于其字节大小,而字节大小又取决于字形的数量、字体元数据(例如Windows 平台的提示)和使用的压缩方法。
网页字体对页面的性能影响
- 页面上使用的字体和字体粗细的总数。
- 页面上使用的字体的总字节大小。
- 字体资源的传输延迟。
- 启动字体下载的时间。
图片资源
常用图片格式:jpeg、png、webp、svg、gif、bmp等。
两种图像格式
- 矢量图形使用线、点和多边形来表示图像,例如SVG。
- 光栅图形通过对矩形网格内每个像素的单独值进行编码来表示图像,例如 PNG、JPEG 或 WebP。

放大的矢量图像 (L) 光栅图像 (R)
WebP
WebP是由 Google 公司开发的一种可提供有损和无损压缩的图片格式,支持透明度,目前已支持动图。
cwebp 命令行工具将图像转换为 WebP
使用 cwebp 的默认压缩设置转换单个文件
cwebp images/flower.jpg -o images/flower.webp使用质量级别 50 转换单个文件
cwebp -q 50 images/flower.jpg -o images/flower.webp转换一个目录中的所有文件
for file in images/*; do cwebp "$file" -o "${file%.*}.webp"; doneImagemin WebP 插件(npm 包)将图像转换为 WebP
const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');
imagemin(['images/*'], {
destination: 'compressed_images',
plugins: [imageminWebp({quality: 50})]
}).then(() => {
console.log('Done!');
});提供兼容 WebP 图像
- 标签语法
<picture>
<source type="image/webp" srcset="flower.webp">
<source type="image/avif" srcset="flower.avif">
<source type="image/jpeg" srcset="flower.jpg">
<img src="flower.jpg" alt="">
</picture>- 脚本语法
<img src={'../.webp'} onError={({ currentTarget }) => {
currentTarget.onerror = null;
currentTarget.src = '../.png';
}}>- 脚本语法
const image = new Image();
image.src = '../.webp';
image.onError = function({currentTarget}) {
currentTarget.onerror = null;
currentTarget.src = '../.png';
}AVIF
AVIF(AV1 Image File Format),即AV1图像文件格式,是一种图像文件格式规范,用于以HEIF容器格式存储使用AV1压缩的图像或图像序列。
白屏时间
当URL对应的网页HTML文档下载完成并开始解析HTML DOM后就触发Tab页面替换操作到首次绘制页面的整个过程时间。
导致原因
- 加载阻塞:当网页HTML文档下载完成开始解析后,原有Tab网页内容消失,开始替换URL对应的HTML文档,此时由于没有完成首次绘制,因此显示为白屏。而此时在解析HTML文档的过程中,开始加载style样式资源或JS脚本资源等,如果加载的过程缓慢,那么白屏时间就延长。
- 执行等待:在解析HTML文档的过程中,当加载style样式资源或JS脚本资源等后,开始解析执行CSS或JS,如果执行过程受阻,那么JS完成第一个事件循环的时间也会延长,直到开始第一次绘制才结束白屏时间。

