浏览器工作原理
从地址栏输入 URL 到页面显示,浏览器依次完成 URL 校验 → DNS 解析 → 网络请求 → HTML/CSS/JS 解析 → 渲染树构建 → 布局 → 绘制 → 合成。理解这条 关键渲染路径(Critical Rendering Path),是掌握浏览器内部机制的基础。
本文按时间顺序梳理各阶段的核心机制,重点放在 DOM/CSSOM 构建与渲染管线。下图按 客户端 → 网络层 → 解析层 → 渲染层 四阶段展示完整流程:
一、客户端:URL 输入与校验
用户在地址栏输入 URL、点击链接或提交表单,都属于 导航(Navigation) 的触发动作。这一步发生在浏览器内部,尚未进入网络请求——浏览器首先要对 URL 做校验和解析,确认可以安全、合法地发起导航后,才会进入 DNS 和网络阶段。
1、URL 解析
浏览器按 URL 标准 将输入字符串解析为结构化对象,主要包含:
| 部分 | 示例 | 说明 |
|---|---|---|
| scheme(协议) | https |
决定使用何种加载机制 |
| host(主机) | example.com |
域名或 IP |
| port(端口) | 443 |
可省略,使用协议默认端口 |
| path(路径) | /docs/page |
资源路径 |
| query(查询) | ?id=1 |
查询参数 |
| fragment(片段) | #section |
仅用于页面内定位,不会发送给服务器 |
用户可能只输入 example.com,浏览器会自动补全为 https://example.com/(具体策略因浏览器和 HSTS 配置而异)。
2、校验与安全检查
解析完成后,浏览器还会做一系列检查,任一环节失败都可能中止导航:
- 协议合法性:
javascript:、data:等特殊 scheme 走不同处理路径,不会发起普通 HTTP 请求 - HSTS 升级:若域名在 HSTS 预加载列表中,强制将
http://升级为https:// - 混合内容拦截:HTTPS 页面中的不安全 HTTP 子资源会被阻止或警告
- Safe Browsing:对照恶意网站/钓鱼名单(如 Google Safe Browsing)
- CSP / 权限策略:部分导航可能受 Content-Security-Policy 约束
只有校验通过后,浏览器才会创建网络请求,进入 DNS 解析阶段。
二、域名解析(DNS)
域名解析就是查找域名对应 IP 地址的过程。浏览器不会每次都走完整 DNS 查询,而是按层级逐级查找缓存:
| 顺序 | 查找位置 | 说明 |
|---|---|---|
| 1 | 浏览器 DNS 缓存 | 命中则直接返回,有时效限制 |
| 2 | 操作系统 DNS 缓存 | 浏览器未命中时由 OS 提供 |
| 3 | 本地 hosts 文件 | 手动配置的域名映射 |
| 4 | 本地 DNS 服务器(递归解析) | 通常由运营商或路由器分配 |
本地 DNS 服务器采用 迭代查询:依次向根域名服务器 → 顶级域名服务器 → 权威域名服务器询问,每一级只返回「下一级该问谁」,不会直接返回最终 IP(除非它自己已有缓存)。
解析成功后,结果会缓存到本地 DNS 服务器;TTL 过期后需重新查询。解析失败则页面无法访问。
三、网络通讯
获取 IP 后,浏览器与服务器建立连接并收发 HTTP 报文。
1、建立连接
- TCP 三次握手:客户端与服务器建立可靠传输通道
- TLS 握手(HTTPS):协商加密算法、验证证书、交换密钥(HTTP 明文传输则跳过此步)
现代浏览器在 HTTPS 场景下还会启用 TLS 1.3,通常 1-RTT 即可完成握手;支持 0-RTT 的站点可在复连时进一步减少延迟。
2、发送与接收
浏览器构造 HTTP 请求报文(方法、路径、Header、Body),经 TCP 封装后依次经过传输层 → 网络层 → 数据链路层 → 物理层到达服务器。
服务器处理请求后返回 HTTP 响应报文(状态码、Header、HTML/CSS/JS 等资源)。部分服务器会先发 103 Early Hints,在 HTML 生成完毕前提示浏览器预加载 CSS/JS,缩短等待时间。
3、连接复用与协议演进
| 协议 | 特点 |
|---|---|
| HTTP/1.1 | 默认 Keep-Alive,同一 TCP 连接复用多个请求 |
| HTTP/2 | 多路复用、Header 压缩、Server Push(已逐步弃用 Push) |
| HTTP/3 | 基于 QUIC(UDP),消除队头阻塞,移动网络更稳定 |
只有在连接关闭或超时后,才会通过 TCP 四次挥手断开连接。
4、资源加载优先级
浏览器会根据资源类型和位置自动分配 fetch priority:
- HTML 文档:最高
<head>中的 CSS、同步<script>:高<img fetchpriority="high">、LCP 图片:可手动提升async/defer脚本、预加载资源:中低lazy图片、低优先级 prefetch:低
四、解析 HTML
HTML 解析分为 词法分析(生成 token)和 语法分析(构建 DOM 树)两个阶段。HTML 解析器不会等待整个文档下载完毕——流式解析会在收到部分字节后就开始构建 DOM,这也是首屏能逐步渲染的基础。
1、词法分析
词法分析将字符流解析成 token。HTML 语法容错性强,无法使用常规编译器式解析器,浏览器采用基于 状态机 的自定义词法分析器。
词法分析器(Tokenizer):负责将输入内容分解成一个个有效标记(token)。
以 <p class="a">text</p> 为例,生成的 token 序列大致为:
| Token 类型 | 内容 |
|---|---|
| 标签开始 | <p |
| 属性 | class="a" |
| 标签结束 | > |
| 文本 | text |
| 结束标签 | </p> |
状态机从 数据状态(Data state) 出发:
- 遇到普通字符 → 进入文本节点
- 遇到
<→ 进入 标签状态- 下一个字符是
!→ 可能是注释或 CDATA - 下一个字符是字母 → 开始标签,持续接收直到
> - 下一个字符是
/→ 结束标签,持续接收直到>,然后回到数据状态
- 下一个字符是
本质上,状态机把每种 token 的「特征字符链」拆成独立状态,再串联成一张状态转移图。
2、语法分析(构建 DOM 树)
树构建器(Tree Builder):根据 HTML 语法规则分析 token 序列,构建 DOM 树。
语法分析的过程就是构建 DOM 树的过程。解析器在消费 token 的同时创建 Document 对象,以它为根节点不断插入子元素。规范定义了每个 token 对应的 DOM 元素;解析器还会维护一个 开放元素栈 来跟踪嵌套关系,并纠正标签嵌套错误。
栈的操作规则(Tree Builder 消费的是 完整 token,不是词法分析阶段的中间态):
- 栈顶元素即当前正在构建的节点
- 遇到 StartTag token(含标签名与全部属性)→ 创建元素、写入属性、挂到父节点后 入栈
- 遇到 Character token(文本)→ 插入为栈顶元素的子节点(不入栈;相邻文本会合并)
- 遇到 Comment token → 作为栈顶元素的子节点
- 遇到 EndTag token → 出栈并校验是否与栈顶标签匹配
3、浏览器的容错机制
浏览 HTML 页面时几乎不会看到「语法无效」的错误提示——浏览器会按 HTML 解析算法 自动修正无效内容并继续解析。例如未闭合的标签、错误的嵌套顺序等,都会被容错处理。
4、遇到 script 标签
HTML 解析器遇到 <script> 时会 暂停 DOM 构建,触发 JavaScript 的下载与执行(详见下一节)。默认情况下,这会阻塞后续 HTML 的解析。
五、解析 JavaScript
HTML 解析器遇到 <script> 时,会触发 JavaScript 的 下载 → 解析 → 编译 → 执行。其中解析阶段与 HTML、CSS 类似,同样分为 词法分析 和 语法分析;编译与执行在 主线程 完成。
下载脚本 → 词法分析(Token)→ 语法分析(AST)→ 编译(字节码)→ 执行 → 恢复 HTML 解析 |
1、词法分析(Tokenize)
词法分析器(Lexer / Scanner)将 JS 源码字符流切分为一个个 Token,不关心语法结构,只识别「词」的边界和类型。
以 const sum = a + 1; 为例:
| Token 类型 | 值 |
|---|---|
| Keyword | const |
| Identifier | sum |
| Punctuator | = |
| Identifier | a |
| Punctuator | + |
| Numeric | 1 |
| Punctuator | ; |
常见 Token 类型包括:关键字(if、return、async)、标识符、字面量(数字、字符串、正则)、运算符、分隔符。
词法分析还会处理:
- 自动分号插入(ASI) 的前置判断
- 模板字符串、正则字面量 等上下文相关 token
- 注释 直接丢弃,不参与后续分析
2、语法分析(Parse → AST)
语法分析器(Parser)根据 ECMAScript 语法规范,将 Token 流组装成 抽象语法树(AST,Abstract Syntax Tree)。每个 AST 节点对应一种语法结构,例如变量声明、函数调用、if 语句等。
Program |
若源码不符合语法规则,Parser 在此阶段抛出 SyntaxError,脚本不会进入执行阶段——这与 HTML 的容错解析不同,JS 语法错误是硬失败。
解析完成后,引擎还会做 早期错误(Early Error) 检查,例如:
let重复声明return出现在非函数顶层- 严格模式下的非法
with语句
3、编译与执行
现代引擎(如 V8)不会直接把 AST 交给解释器逐行运行,典型流水线为:
- Parser 生成 AST
- Ignition 将 AST 编译为 字节码(Bytecode) 并执行
- TurboFan 对热点代码做 JIT 优化,生成高效机器码
源码 → AST → 字节码 →(热点)优化机器码 |
执行期间可以读写 DOM、触发重排重绘、注册事件监听等——都在主线程上进行,因此长时间运行的 JS 会阻塞页面响应。
4、script 标签与 HTML 解析的阻塞
默认情况下,普通 <script> 或 <script src="..."> 会:
- 阻塞 HTML 解析——解析器暂停,等待脚本下载
- 阻塞 DOM 构建——脚本执行期间不能继续插入节点
- 可能阻塞渲染——若脚本修改 DOM 或读取布局属性
CSS 也会阻塞脚本执行:浏览器需先完成样式表解析,才能执行依赖样式的 JavaScript(避免 JS 读到错误的样式计算结果)。
5、async 与 defer
通过 async 和 defer 属性,可以改变脚本的 下载时机 和 执行时机,从而减少对 HTML 解析的阻塞:
| 属性 | 下载 | HTML 解析 | 执行时机 | 执行顺序 |
|---|---|---|---|---|
| 默认(无属性) | 同步,阻塞解析 | 暂停 | 下载完立即解析并执行 | 按文档顺序 |
defer |
异步,不阻塞解析 | 继续 | DOM 解析完成后、DOMContentLoaded 前 |
按文档顺序 |
async |
异步,不阻塞解析 | 继续 | 下载完立即解析并执行 | 不保证顺序 |
type="module" |
异步(类似 defer) | 继续 | DOM 解析完成后 | 按文档顺序,支持 ES Module |
注意:
async和defer只影响 何时下载、何时执行,不影响 JS 本身的词法/语法分析过程——一旦开始执行,解析流程相同。
<!-- 默认:下载与执行期间阻塞 HTML 解析 --> |
执行时序对比(简化):
默认 script: [HTML]──停──[下载 → 解析 → 执行 JS]──[继续 HTML]──[DOMContentLoaded] |
6、其他加载方式
type="module":默认 defer 语义,自动严格模式,支持import/export- 动态
import():运行时按需加载,不阻塞 HTML 解析 document.createElement('script'):动态插入的脚本默认等效于async
六、解析 CSS
构建 DOM 树的过程中遇到 CSS 资源会同步解析。CSS 同样经过词法分析和语法分析,最终生成 CSSOM 树(CSS Object Model)。
样式来源主要有三种:
<link>引用的外部 CSS 文件<style>标签内的 CSS- 元素
style属性中的内联 CSS
1、词法分析
浏览器使用 Flex 等工具将 CSS 字符流解析为 token。
2、语法分析(构建 CSSOM 树)
解析器将 token 组装成 StyleSheet 对象,每个对象包含多条 CSS 规则;每条规则包含一个选择器和一组属性声明。
StyleSheet |
可在 DevTools Console 中通过 document.styleSheets 查看解析结果。
3、生成选择器索引(Rule Map)
为加速样式匹配,浏览器将 CSS 规则按 最右侧选择器 的类型(id、class、标签、伪类)分别存入哈希表:
CompactRuleMap m_idRules; |
匹配时先按 id → class → 伪元素 → 标签的顺序快速取出候选规则,再逐条检查复合选择器左侧是否也匹配当前元素。
4、CSS 阻塞渲染
CSS 是 渲染阻塞资源:浏览器必须等 CSSOM 构建完成,才能生成渲染树并进行首次绘制。@import 会串行触发额外请求,进一步延迟 CSSOM 的构建。
七、构建渲染树
1、渲染树的概念
DOM 树描述文档结构,CSSOM 树描述样式规则;渲染树(Render Tree) 则是二者的交集——只包含需要显示的节点及其计算后的样式。
Firefox 称渲染树中的节点为 Frame,WebKit/Blink 称为 RenderObject。每个 RenderObject 对应一个矩形区域,其类型由 display 等 CSS 属性决定:
RenderObject* RenderObject::createObject(Node* node, RenderStyle* style) |
2、构建过程
从 DOM 根节点 document 开始 深度优先遍历,对每个可见节点:
- 在 Rule Map 中匹配适用的 CSS 规则
- 计算最终样式(含继承、层叠、默认值)
- 创建对应的 RenderObject 加入渲染树
2.1 计算样式
1)选择器匹配
对每个节点,按 id → class → 伪元素 → 标签 → 通配符的顺序取出候选规则。若复合选择器最右侧匹配,再递归检查左侧选择器是否也匹配。
2)设置 style
找到匹配规则后,计算各 CSS 属性的最终值:
- 继承属性:沿 DOM 树向上查找祖先元素的值,找不到则用初始值
- 层叠合并:多个规则命中同一节点时,按优先级(!important > 内联 > ID > class > 标签 > 通配符)合并
- 样式结构共享:浏览器将常用样式组合缓存为共享对象,减少内存占用
3)调整 style
部分属性会触发其他属性的自动调整,例如 position: absolute/fixed 或 float 的元素会被计算为 display: block。
2.2 渲染对象与 DOM 元素的关系
- 多对一:多个 DOM 节点可能不产生 RenderObject(如
<head>、display: none的元素);visibility: hidden的元素仍在渲染树中,只是不可见 - 一对多:复杂元素可能对应多个 RenderObject,如
<select>有显示区、下拉列表、按钮三个渲染对象;文本换行时每一行也会新增 RenderObject - 位置不同:浮动和绝对定位元素的 RenderObject 会脱离正常文档流,在原位置留下占位框架
八、布局(Layout / Reflow)
渲染树构建完成后,浏览器需要为每个 RenderObject 计算 几何信息(位置、尺寸),这个过程称为 布局 或 重排(Reflow)。
布局从根 RenderObject(对应 <html> 元素)开始,递归遍历渲染树,为每个节点计算坐标和大小。
HTML 默认采用 流式布局(Flow Layout):大多数情况下只需一次从上到下、从左到右的遍历即可确定几何信息——靠后的元素通常不影响靠前元素的位置。例外情况包括表格布局、Flex/Grid 中的 auto 尺寸等,可能需要多轮计算。
坐标系以根 RenderObject 为原点,左上角为 (0, 0),尺寸等于 视口(Viewport) 大小。
触发重排的操作
| 类型 | 示例 |
|---|---|
| DOM 变更 | 增删节点、修改文本 |
| 几何样式 | width、height、margin、padding、border |
| 布局上下文 | display、position、float |
| 读取布局属性 | offsetWidth、getBoundingClientRect() 强制同步布局 |
频繁交替读写布局属性会导致 布局抖动(Layout Thrashing)——浏览器被迫在读写之间反复同步布局计算。
九、绘制与合成
1、绘制(Paint)
布局完成后,浏览器遍历渲染树,将各节点绘制为位图或绘制指令列表。绘制按 绘制顺序(Paint Order) 进行,受 z-index、stacking context 等影响。
绘制通常分多个阶段:背景 → 边框 → 内容 → 阴影等,最终生成 绘制记录(Paint Record)。
2、合成(Composite)
现代浏览器会将页面拆分为多个 合成层(Compositing Layer),由 合成线程(Compositor Thread) 在 GPU 上完成最终合成。
以下情况可能触发独立合成层:
transform、opacity动画will-change: transform提示<video>、<canvas>、<iframe>等元素- 3D transform(
translateZ(0))
只改变合成层属性时,可跳过 Layout 和 Paint,直接 Composite。
十、完整流程回顾
- 客户端:用户输入 URL,浏览器解析并校验(协议、HSTS、安全检查)
- DNS 解析:浏览器缓存 → OS 缓存 → hosts → 本地 DNS 迭代查询,获取服务器 IP
- TCP 三次握手建立连接;HTTPS 还需 TLS 握手
- 浏览器发送 HTTP 请求;可选 103 Early Hints 提前预加载
- 服务器返回 HTTP 响应(HTML 及关联资源)
- 浏览器 流式解析 HTML,构建 DOM 树
- 并行 解析 CSS 构建 CSSOM 树;遇到 JavaScript 下载并执行(
async/defer可改变阻塞行为) - 合并 DOM 与 CSSOM,构建 渲染树(排除非视觉节点和
display: none元素) - 布局:计算每个节点的几何信息
- 绘制:生成绘制指令
- 合成:GPU 将各层合成为最终画面,显示到屏幕
参考链接