我对浏览器渲染的理解分了几次补:起初只记得从 URL 到页面的大致流程,后来才弄清每个阶段各自在解决什么问题,最后能按需要拆到某一步的细节。
Q1: 从输入 URL 到页面展示,经历了哪些步骤?
1
URL 解析与导航开始
用户在地址栏输入内容后,浏览器首先判断是搜索关键词还是 URL。如果包含
. 且没有空格,浏览器会补全协议(默认 https://)并开始导航。浏览器主进程通过 IPC 通知网络进程发起请求。2
DNS 解析
浏览器先查自身 DNS 缓存,再查操作系统缓存和 hosts 文件,最后由本地 DNS 服务器执行迭代查询——根 DNS 返回
.com 服务器地址,.com DNS 返回权威服务器地址,权威 DNS 返回最终的 A 记录。3
TCP 三次握手与 TLS 握手
客户端与服务器通过三次握手建立 TCP 连接(SYN → SYN+ACK → ACK)。HTTPS 场景下还需要 TLS 握手——TLS 1.3 优化为 1-RTT,支持 0-RTT 恢复。QUIC(HTTP/3)将传输层握手和 TLS 握手合并,首次连接只需 1-RTT。
4
HTTP 请求与服务器响应
浏览器发送 HTTP 请求报文(请求行 + 请求头 + 请求体),服务器返回响应(状态码 + 响应头 + 响应体)。HTTP/1.1 有队头阻塞,HTTP/2 多路复用解决 HTTP 层阻塞,HTTP/3 用 QUIC 解决 TCP 层阻塞。
5
浏览器解析与渲染
渲染进程将 HTML 解析为 DOM 树,CSS 解析为 CSSOM 树,两者合并为渲染树。然后经过 Layout(几何计算)、Paint(绘制指令)、Composite(GPU 合成)三个阶段,最终输出到屏幕。
浏览器渲染的本质是把 HTML/CSS 字节流经过解析、布局、绘制、合成,最终变成屏幕上的像素。
没有渲染流程
如果浏览器收到 HTML 后直接尝试绘制像素,就像一个建筑工人拿到一摞建材清单后直接砌墙——没有图纸(DOM 树)、没有结构设计(Layout),最终只能得到一堆乱七八糟的砖块。
引入渲染流程
有了渲染流程后,就像建筑工地有了完整的流程:先读清单建骨架(DOM),再画装修图纸(CSSOM),然后出施工图(Layout),最后按图施工(Paint)并交付(Composite)。每一步职责清晰,层层递进。
Q2: URL 解析与导航开始
用户在地址栏输入内容后,浏览器做了什么?. 且没有空格,浏览器会补全协议(默认 https://)并开始导航。浏览器主进程(Browser Process)通过 IPC 通知网络进程(Network Process)发起请求,同时会触发 beforeunload 事件给当前页面,如果当前页面有未保存的表单数据,可以在此阶段拦截导航。
导航开始时,浏览器还会检查 HSTS(HTTP Strict Transport Security)列表,如果目标域名在列表中,会强制将 http:// 升级为 https://,避免中间人攻击。
Q3: DNS 解析:域名到 IP 的转换
浏览器怎么把www.example.com 变成 93.184.216.34?
chrome://net-internals/#dns 可查看),缓存 TTL 通常与 DNS 记录一致;再查操作系统缓存(getaddrinfo 系统调用)和 hosts 文件(/etc/hosts);最后由本地 DNS 服务器(通常由 ISP 或 8.8.8.8 等公共 DNS 提供)执行迭代查询。
DNS 缓存机制
DNS 解析结果会被缓存在多个层级:
浏览器查询 DNS 时,先查浏览器缓存,再查操作系统缓存和 hosts 文件,最后才问本地 DNS 服务器。如果本地 DNS 服务器有缓存且未过期,直接返回,不需要迭代查询。
DNS 劫持和 DNS-over-HTTPS
DNS 劫持是指攻击者篡改 DNS 解析结果,把用户引导到恶意网站。常见手段:运营商 DNS 劫持、中间人攻击、路由器劫持。 DNS-over-HTTPS(DoH)将 DNS 查询封装在 HTTPS 请求中,加密传输,防止中间人篡改。Chrome 从 2020 年开始默认启用 DoH(chrome://settings/security → 使用安全 DNS)。
Q4: TCP 三次握手与 TLS 握手
客户端和服务器怎么建立连接?为什么不能两次握手?
两次握手的问题是:服务器回复 SYN+ACK 时,客户端还没确认自己能收到服务器的数据。如果只握手两次,服务器不知道客户端能不能收到自己的回复,可能导致服务器单方面建立连接——客户端根本没收到服务器的响应,但服务器以为连接已建立,白白浪费资源。 三次握手的第三步(ACK)让服务器确认客户端能收到自己的回复,双方都确认收发能力后才建立连接。TCP 四次挥手的过程
TLS 1.2 和 1.3 的区别
TLS 1.3 的核心优化是将密钥交换和证书验证合并到同一批消息——客户端在 ClientHello 时就附带 DH 密钥共享,服务器在 ServerHello 时就完成密钥交换。这减少了 1 个 RTT。
0-RTT 恢复的风险
什么是 RTT? RTT(Round-Trip Time)是数据从客户端到服务器再回来的时间。一个 RTT 大约 50-200ms(取决于距离)。TLS 握手需要 1-2 个 RTT,意味着每次 HTTPS 连接要多等 100-400ms。 什么是 0-RTT? 0-RTT 是 TLS 1.3 的特性——客户端在第一个数据包中就携带应用数据,不需要等服务器回复。就像你打电话时,不等对方接就直接说”帮我订一杯咖啡”,对方接起电话就听到了,省了一个来回的时间。 风险是什么? 攻击者可以截获你的 0-RTT 数据包,复制一份发给服务器。服务器无法区分是新请求还是重放的旧请求——如果你发的是”转 100 元”,攻击者重放一次就多转了 100 元。 所以: 0-RTT 只适合幂等操作(GET 请求,重复执行结果一样),不适合敏感操作(POST、支付、转账)。Q5: HTTP 请求与服务器响应
浏览器和服务器之间怎么交换数据?Content-Type 决定浏览器如何解析响应体(text/html 走渲染流程,application/json 走 JS 处理)。HTTP/1.1 曾提出管线化(pipelining),但响应必须按请求顺序返回,且主流浏览器默认关闭,实际并未解决队头阻塞。
HTTP 缓存策略:强缓存 vs 协商缓存
浏览器缓存分为强缓存(不发请求,直接用缓存)和协商缓存(发请求问服务器,服务器说用缓存就用缓存)。
ETag vs Last-Modified:ETag 基于内容哈希,精确到字节级别;Last-Modified 基于修改时间,精度只到秒。ETag 优先级更高——如果同时存在,浏览器优先用 ETag。
HTTP 状态码详解
跨域解决方案(CORS)
浏览器的同源策略限制了跨域请求。CORS(Cross-Origin Resource Sharing)是 W3C 标准的跨域解决方案:Q6: 浏览器解析:从字节流到渲染树
浏览器拿到 HTML 字节后,怎么变成可以渲染的树结构?display:none 的节点(不在渲染树中),但保留 visibility:hidden 的节点(在渲染树中但不绘制)。伪元素(::before/::after)在渲染树中生成匿名节点。
CSS 阻塞渲染的优化策略
CSS 阻塞渲染是因为浏览器需要 CSSOM 才能构建渲染树。优化策略:- 内联关键 CSS:把首屏需要的 CSS 内联到
<style>标签中(控制在 14KB 以内,匹配 TCP 初始拥塞窗口) - 异步加载非关键 CSS:用
<link rel="preload" as="style" onload>异步加载非首屏 CSS - CSS 放
<head>:尽早开始下载,不要放在<body>底部
JS 阻塞 DOM 解析的优化策略
JS 阻塞 DOM 解析是因为 JS 可能调用document.write() 重写文档,或者访问已解析的 DOM 节点。优化策略:
defer:不阻塞 DOM 解析,DOM 解析完后按文档顺序执行(推荐)async:不阻塞 DOM 解析,下载完立即执行(适合独立脚本)type="module":默认 defer 行为,按依赖图顺序执行
渲染树和 DOM 树的区别
渲染树是 DOM 树和 CSSOM 树的合并结果。
display:none 的节点不在渲染树中(不占位),visibility:hidden 的节点在渲染树中(占位但不绘制)。
Q7: 渲染流程:Layout → Paint → Composite
渲染树构建完成后,怎么变成屏幕上的像素?transform 和 opacity 动画在此阶段执行,不需要重排重绘,性能最好。
重排(Reflow)的触发条件
重排需要重新计算节点的几何信息,代价最高。触发条件:- DOM 增删(插入/删除节点)
- 元素尺寸变化(
width、height、padding、margin、border) - 内容变化(文本增减、图片尺寸变化)
- 浏览器窗口 resize
- 读取布局属性(
offsetWidth、offsetHeight、getComputedStyle、getBoundingClientRect)
重绘(Repaint)的触发条件
重绘只需要重新生成绘制指令,不影响布局,代价较低。触发条件:- 颜色变化(
color、background-color、border-color) - 阴影变化(
box-shadow、text-shadow) - 可见性变化(
visibility、outline) - 文本装饰变化(
text-decoration)
Composite 阶段的优化
合成阶段由 GPU 执行,只需要矩阵变换,不需要重排重绘。触发合成的操作:transform(位移、缩放、旋转)opacity(透明度)will-change(提前告知浏览器哪些属性会变化)
transform 做位移/缩放/旋转动画,用 opacity 做透明度动画,避免触发布局属性(width、height、margin、padding)变化。
Q8: script 标签的加载方式有什么区别?
底层源码:Chromium 是如何实现的?
Blink 的渲染流水线是怎么实现的?
Blink 的渲染流水线在 LocalFrame::ScheduleVisualUpdate() 中触发一帧渲染(详见 Blink 源码 local_frame.cc)。每一帧的执行顺序是:
- Style Calculation:计算每个节点的最终样式(
StyleEngine::RecalcStyle()) - Layout:计算几何信息(
LayoutView::Layout()) - Paint:生成绘制指令(
PaintController::Paint()) - Composite:GPU 合成(
LayerTreeHost::Composite())
渲染树和 DOM 树有什么区别?
DOM 树包含所有节点(包括 display:none 的节点),渲染树只包含可见节点。渲染树是 DOM 树和 CSSOM 树的合并结果,每个节点都有计算后的完整样式信息。详见 Blink 源码 style_engine.cc。
及格回答 vs 满分回答
问题:“从输入 URL 到页面展示,发生了什么?”
及格回答(60-70分):“浏览器先做 DNS 解析,然后建立 TCP 连接,发送 HTTP 请求,服务器返回 HTML,浏览器解析 HTML 构建 DOM 树,再解析 CSS 构建 CSSOM 树,两者合并为渲染树,然后 Layout、Paint、Composite,最终显示在屏幕上。”问题出在:背了流程但不理解每一步的细节。 满分回答(90-100分):
从输入 URL 到页面展示,浏览器要经历 7 个阶段。首先是网络阶段:URL 解析(补全协议、检查 HSTS)、DNS 解析(浏览器缓存 → 操作系统缓存 → 本地 DNS 迭代查询)、TCP 三次握手(SYN → SYN+ACK → ACK)、TLS 握手(TLS 1.3 优化为 1-RTT,支持 0-RTT 恢复)、HTTP 请求/响应(HTTP/2 多路复用解决 HTTP 层队头阻塞,HTTP/3 用 QUIC 解决 TCP 层队头阻塞)。Tali Garsiel 和 Paul Irish 在经典文章《How Browsers Work》中首次系统性地将浏览器内部分为解析、渲染、合成三大模块,为后续所有浏览器原理文章奠定了框架基础。[1]
然后是渲染阶段:HTML 解析器采用增量解析(边下载边解析),将字节流 Tokenize 为 Token,再由 TreeBuilder 构建 DOM 树;CSS 解析器独立构建 CSSOM 树,两者合并为渲染树(排除 display:none 的节点)。Layout 阶段计算每个节点的精确几何信息(盒模型、Flexbox/Grid 子元素分配),Paint 阶段按 Stacking Context 规则生成绘制指令列表,Composite 阶段由 GPU 合成器将多个图层合并为最终画面。李兵在极客时间《浏览器工作原理与实践》中将渲染流水线拆解为 Style Calculation → Layout → Paint → Composite 四个阶段,并指出 Chrome 为每帧分配约 16.6ms,其中 JS 执行约 5ms、渲染约 11ms,这个时间分配直接影响动画流畅度。[2]
渲染阶段有两个关键的性能陷阱:一是 CSS 阻塞渲染但不阻塞 DOM 解析——CSS 文件加载完成前,渲染树无法构建,页面不会渲染,但 HTML 解析器可以继续构建 DOM 树。二是 JS 阻塞 DOM 解析——浏览器遇到<script>标签时会暂停 HTML 解析,等待 JS 下载并执行完毕。优化策略:CSS 放<head>尽早加载,JS 用defer避免阻塞 DOM 解析,transform/opacity动画走合成器不走主线程。[3]
常见追问
CSS 会阻塞 DOM 解析吗?JS 会阻塞渲染吗?
CSS 会阻塞 DOM 解析吗?JS 会阻塞渲染吗?
答题要点: CSS 不阻塞 DOM 解析但阻塞渲染,JS 阻塞 DOM 解析。高分回答: “CSS 不阻塞 DOM 解析——HTML 解析器可以继续构建 DOM 树,只是不能渲染(因为 CSSOM 没就绪,渲染树无法构建)。JS 阻塞 DOM 解析——浏览器遇到 这是因为 JS 可能调用
<script> 标签时会暂停 HTML 解析,等待 JS 下载并执行完毕。document.write() 重写文档,或者通过 document.querySelector 访问已解析的 DOM 节点,浏览器无法预知脚本行为,只能保守地暂停。”什么是关键渲染路径(CRP)?怎么优化?
什么是关键渲染路径(CRP)?怎么优化?
答题要点: CRP 是从收到 HTML 到首次渲染的路径,优化关键是减少关键资源数量和缩短关键路径长度。高分回答: “关键渲染路径(Critical Rendering Path)是从收到 HTML 到首次渲染的路径:HTML → DOM → CSSOM → 渲染树 → Layout → Paint。优化策略:1) 内联关键 CSS(控制在 14KB 以内,匹配 TCP 初始拥塞窗口);2) 延迟非关键 CSS 用
<link rel='preload' as='style' onload> 异步加载;3) JS 用 defer 避免阻塞 DOM 解析;4) SSR/SSG 直出 HTML 让浏览器立即开始解析。”重排和重绘有什么区别?怎么减少重排?
重排和重绘有什么区别?怎么减少重排?
答题要点: 重排是重新计算布局,重绘是重新生成绘制指令。重排比重绘代价高得多。高分回答: “重排(Reflow)需要重新计算节点的几何信息(位置、大小),影响当前节点及其所有子节点,代价最高。重绘(Repaint)只需要重新生成绘制指令,不影响布局,代价较低。减少重排的方法:1) 批量修改样式(用 class 一次性改);2) 避免读取布局属性(
offsetWidth、getComputedStyle)后立即修改样式(会触发强制同步布局);3) 用 transform/opacity 做动画,走合成器不走主线程。”HTTP/2 一定比 HTTP/1.1 快吗?
HTTP/2 一定比 HTTP/1.1 快吗?
答题要点: 不一定。HTTP/2 解决了 HTTP 层的队头阻塞,但 TCP 层的队头阻塞仍在。高分回答: “不一定。HTTP/2 的多路复用解决了 HTTP 层的队头阻塞,但 TCP 层的队头阻塞仍在——一个 TCP 包丢失会阻塞该连接上所有流。在高丢包率的网络环境下(如移动网络),HTTP/2 可能比 HTTP/1.1 更慢,因为 HTTP/1.1 的 6 个连接是独立的,一个连接丢包不影响其他连接。HTTP/3 用 QUIC(基于 UDP)为每个 Stream 独立处理丢包,彻底解决了这个问题。”
DNS 递归查询和迭代查询有什么区别?浏览器用哪种?
DNS 递归查询和迭代查询有什么区别?浏览器用哪种?
答题要点: 浏览器到本地 DNS 是递归查询,本地 DNS 到根/权威 DNS 是迭代查询。高分回答: “递归查询是’我帮你查到底’——浏览器问本地 DNS,本地 DNS 负责从根 DNS 一直问到权威 DNS,最后把结果返回给浏览器。迭代查询是’我告诉你下一步问谁’——根 DNS 不直接查,而是告诉本地 DNS ‘你去问 .com DNS’,本地 DNS 再问 .com DNS,.com DNS 说’你去问 example.com 权威 DNS’,最后权威 DNS 返回结果。浏览器到本地 DNS 是递归查询,本地 DNS 到根/权威 DNS 是迭代查询。”
TCP 三次握手和四次挥手的过程是什么?为什么不能两次握手?
TCP 三次握手和四次挥手的过程是什么?为什么不能两次握手?
答题要点: 三次握手同步初始序列号并确认收发能力,四次挥手确保双方数据都发完。高分回答: “三次握手:客户端发 SYN(Seq=x),服务器回 SYN+ACK(Seq=y, Ack=x+1),客户端发 ACK(Ack=y+1)。为什么不能两次?因为服务器回复 SYN+ACK 时,客户端还没确认自己能收到服务器的数据——如果只握手两次,服务器不知道客户端能不能收到自己的回复,可能导致服务器单方面建立连接。四次挥手:客户端发 FIN,服务器回 ACK(此时服务器可能还有数据没发完),服务器发 FIN,客户端回 ACK。为什么是四次而不是三次?因为 TCP 是全双工的——关闭发送通道和接收通道是独立的操作。”
TLS 握手过程是什么?TLS 1.2 和 1.3 有什么区别?
TLS 握手过程是什么?TLS 1.2 和 1.3 有什么区别?
答题要点: TLS 1.2 需要 2-RTT,TLS 1.3 优化为 1-RTT,支持 0-RTT 恢复。高分回答: “TLS 1.2 握手:客户端发 ClientHello(支持的加密套件列表),服务器回 ServerHello(选定的加密套件)+ 证书,客户端验证证书 + 交换密钥,双方生成会话密钥。需要 2-RTT。TLS 1.3 优化:将密钥交换和证书验证合并到前两条消息,客户端在 ClientHello 时就附带 DH 密钥共享,服务器在 ServerHello 时就完成密钥交换。只需 1-RTT。还支持 0-RTT 恢复——通过 Pre-Shared Key 在第一个数据包中就携带应用数据,但有重放攻击风险。”
Composite 阶段的优化原理是什么?为什么 transform/opacity 动画性能最好?
Composite 阶段的优化原理是什么?为什么 transform/opacity 动画性能最好?
答题要点: Composite 阶段由 GPU 合成器执行,只需要矩阵变换,不需要重排重绘。高分回答: “Composite 阶段由 GPU 合成器将多个图层(Composited Layers)合并为最终画面。当元素被提升为独立图层后(通过 优化策略:用
will-change 或 transform),后续的 transform/opacity 变化只需要 GPU 做矩阵变换,不需要重排(Layout)也不需要重绘(Paint)。transform 做位移/缩放/旋转动画,用 opacity 做透明度动画,避免触发布局属性(width、height、margin、padding)变化。“延伸阅读与引用参考
- How Browsers Work:Tali Garsiel & Paul Irish 的经典文章,系统梳理了解析、渲染树、布局与绘制的完整流程,是浏览器原理文章的重要参考。[1]
- High Performance Browser Networking:Ilya Grigorik 的免费在线书籍,涵盖 DNS、TCP、TLS、HTTP 全链路优化,是网络层性能优化的权威参考。[2]
- Inside look at modern web browser:Lin Clark 在 Chrome Developers 发表的四部曲系列(2018),用漫画解析 Chromium 多进程架构、渲染流水线和合成器工作原理。
- 李兵 极客时间《浏览器工作原理与实践》:将渲染流水线拆解为 Style → Layout → Paint → Composite 四阶段,给出每帧 16.6ms 的时间分配模型(JS 5ms + 渲染 11ms)。
- 《HTTP 图解》:上野宣著,用图解方式讲解 HTTP 协议的核心知识。
- MDN Web Docs - 渲染性能:MDN 对浏览器渲染性能的权威指南。
- Cloudflare Blog - HTTP/3:Cloudflare 对 HTTP/3 和 QUIC 协议的深度解析,含 0-RTT 恢复的安全风险分析。
