当你输入地址按下回车的时候,有没有思考过页面为什么会出现对应的内容?本文将带你搞懂从从输入 URL 到页面渲染的完整过程。
1.导航阶段 (Navigation)
1.1用户输入与解析
用户在地址栏输入 URL,浏览器会解析这个 URL,判断是搜索内容还是一个合法的网址。
1.2DNS 解析:从域名到 IP 地址
浏览器首先进行各级缓存查询(浏览器缓存、操作系统缓存、路由器缓存、ISP 缓存),看是否有该域名对应的 IP 地址。如果都没有,则会向根域名服务器发起递归查询,最终获取到目标服务器的 IP 地址。
1.3建立 TCP 连接:三次握手
浏览器通过获取的 IP 地址和端口号(HTTP 默认 80,HTTPS 默认 443),与服务器进行“三次握手”,建立一个可靠的 TCP 连接。
- 客户端发送 SYN 包(同步序列编号)
- 服务器响应 SYN-ACK 包
- 客户端回复 ACK 包,连接建立完成
TLS 握手:如果是 HTTPS 协议,还需要在 TCP 连接之上进行 TLS/SSL 握手,协商加密密钥,建立安全的加密通道。
1.4发送 HTTP 请求
浏览器构建一个 HTTP 请求报文(包含请求行、请求头、请求体),通过建立好的连接发送给服务器。
Network面板观测
浏览器开发者工具的Network面板可以清晰地看到这个阶段的耗时:
- DNS Lookup:DNS 解析时间
- Initial Connection:TCP 连接建立时间
- SSL/TLS Handshake:TLS 握手时间(仅 HTTPS)
- TTFB (Time to First Byte) :从请求发出到收到服务器第一个字节响应的时间,是关键性能指标
2.响应与解析阶段(Response & Parsing)
2.1服务器处理与响应
服务器接收请求后,执行相应的业务逻辑(查询数据库、处理业务等),然后返回 HTTP 响应报文。
2.2解析 HTML构建 DOM 树
浏览器接收到 HTML 后,渲染引擎会自上而下逐行解析,生成一个树状结构的 DOM (Document Object Model) 对象。
2.3解析 CSS 构建 CSSOM 树
遇到 <link>标签或 <style>标签时,浏览器会加载并解析 CSS,生成 CSSOM (CSS Object Model)树。CSS 解析通常不会阻塞 DOM 解析。
2.4JavaScript 的执行
如果遇到script>标签,浏览器会暂停 HTML 的解析,转而去下载并执行 JavaScript。因为 JS 可能会修改 DOM 和 CSSOM,所以需要阻塞以保证后续解析的正确性。可通过 defer(延迟执行)或 async(异步执行)属性改变此行为。
Performance面板观测
开发者工具的Performance面板可以录制加载过程,观察到Parse HTML,Parse Stylesheet等事件。
3.渲染阶段 (Rendering)
浏览器通过解析 HTML 和 CSS 构建 DOM 和 CSSOM 树,然后结合它们生成渲染树,接着通过回流(计算布局)和重绘(绘制像素)将页面呈现出来。
3.1构建渲染树 (Render Tree):
将 DOM 树和 CSSOM 树结合起来,,生成只包含可见节点及其样式的渲染树(如 display: none的节点不会包含在内)。
3.2布局/回流 (Reflow / Layout)
根据渲染树计算出每个节点在屏幕上的精确位置和大小,当渲染树中部分或全部元素的尺寸、结构或位置发生改变时,浏览器需要重新计算元素的几何属性(位置和大小),这个过程称为回流。
触发条件:
- 页面首次渲染。
- DOM 节点的新增或删除。
- 元素尺寸或位置的改变(width,height,padding,margin,border,top,left等)。
- 浏览器窗口resize。
- 内容改变,如文本改变或图片大小改变,导致布局变化。
- 获取特定的布局信息,例如读取offsetTop,offsetLeft,offsetWidth,offsetHeight,scrollTop,scrollLeft,scrollWidth,scrollHeight,clientTop,clientWidth,getComputedStyle()等。为了返回最精确的值,浏览器必须立即执行一次回流操作,这被称为强制同步回流。
3.3重绘 (Repaint / Paint)
根据回流阶段计算出的信息,将每个节点绘制到屏幕上,包括文本、颜色、边框、阴影等。当元素的视觉表现发生变化(如颜色、背景、可见性等),但其几何属性没有改变时,浏览器会重新绘制该元素,这个过程称为重绘。
回流必然导致重绘,但重绘不一定需要回流。
性能优化
回流的成本远高于重绘,是性能优化的重点关注对象。可以通过以下策略进行优化:
- 读写分离,避免强制同步布局: 先集中读取所有需要的值,然后再一次性地集中修改样式。
// 错误: 读写穿插,触发多次回流
div.style.left = div.offsetLeft + 10 + 'px';
div.style.top = div.offsetTop + 10 + 'px';
// 正确: 读写分离
const left = div.offsetLeft;
const top = div.offsetTop;
div.style.left = left + 10 + 'px';
div.style.top = top + 10 + 'px';
- 批量处理 DOM 操作: 对于需要多次操作 DOM 的场景,可以先将元素
display: none,操作完毕后再显示出来。或者使用DocumentFragment作为临时容器,在内存中完成所有 DOM 修改,最后一次性地追加到真实 DOM 中。
// 使用 DocumentFragment 在内存中操作
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);
-
使用 CSStransform和opacity实现动画: 这两个属性的变化通常可以由 GPU 直接处理(通过合成层Compositing),从而绕开回流和重绘,实现更流畅的动画效果。这也是所谓的硬件加速。
-
避免使用 table 布局: table布局中任何单元格的改动都可能触发整个表格的回流,开销较大。应优先使用 Flexbox 或 Grid 布局。
-
将频繁回流的元素提升为独立图层: 使用will-change或transform: translateZ(0)等 hack 手段,可以提示浏览器为该元素创建一个独立的合成层。这样,该元素的回流和重绘将被限制在该图层内部,不会影响到其他元素。但需注意,滥用图层会消耗大量内存。
3.4合成 (Compositing)
浏览器将页面分为多个图层进行管理。当某一图层发生变化时,只需重新绘制该层,然后与其他图层合成,无需触发整个页面的回流与重绘,大幅提升性能。
Performance面板观测
Performance面板中的Recalculate Style,Layout,Paint,Composite Layers事件详细记录了这一阶段的开销。