作为前端开发者,我们每天都在和浏览器打交道,处理页面加载、资源渲染、网络请求。但你有没有静下心来想过------当用户在地址栏输入一个 URL,按下回车之后,到底发生了什么?
这篇文章会从用户敲下回车键开始,一步步揭开浏览器背后的黑盒,带你完整走一遍页面加载的全过程。
1. URL 解析与语法校验
用户输入的可能是一个完整的 URL,也可能是个关键词。浏览器首先会判断输入的字符串是不是一个合法的 URL。
-
如果是完整的 URL(如 juejin.cn/post/123456...
-
如果是搜索关键词(如 "掘金 前端"),浏览器会将其转为搜索请求(一般是你默认的搜索引擎)。
对合法的 URL,浏览器会进行解析,将其拆分为不同的组成部分:
bash
https://juejin.cn:443/post/123456?from=home#comments
| | | | | |
协议 主机名 端口 路径 查询参数 锚点
2. DNS 解析:找到目标 IP 地址
浏览器需要知道 juejin.cn 对应的 IP 地址,才能和服务器建立连接。它会进行 DNS 查询,这个过程可能经过以下几个地方:
-
浏览器缓存:之前访问过吗?缓存里有没有 IP?
-
操作系统缓存:系统层面有没有记录?
-
本地 hosts 文件:开发时配置过静态 IP 映射?
-
DNS 服务器:最终会走向配置的 DNS 服务(如 114.114.114.114 或 8.8.8.8)
如果缓存未命中,浏览器通过递归或迭代查询,向根域名服务器、顶级域名服务器、权威域名服务器层层请求,最终拿到 IP 地址。
备注:baidu.com 这样的网址其实是不完整的,baidu.com. 后面还有一个"."代表根域名服务器,从右往左进行dns解析
3. 建立 TCP 连接(三次握手)
拿到 IP 后,浏览器需要与服务器建立连接。这里涉及到 TCP 三次握手(为的是确保双方都有"发送/响应请求"的能力,八股偷偷输出一波嘿嘿):
-
客户端 → 服务器:发送 SYN 报文,请求建立连接。
-
服务器 → 客户端:回应 SYN + ACK,表示同意。
-
客户端 → 服务器:再发送 ACK,确认连接建立。
至此,TCP 连接建立完成。如果是 HTTPS,还会接着进行 TLS 握手,交换密钥,完成加密通信设置。
4. 发送 HTTP 请求
连接建立后,浏览器发送 HTTP 请求。请求报文包括:
-
请求行(如:GET /post/123456 HTTP/1.1)
-
请求头(如:Host, User-Agent, Cookie 等)
-
请求体(POST 请求会包含)
此时,请求被发往服务器,准备获取页面内容。
5. 服务器处理请求并响应
服务端收到请求后,会根据请求路径和参数进行处理。可能涉及:
-
路由分发
-
查询数据库
-
拼接模板
-
调用后端接口
-
渲染 HTML 等等
最终,服务器生成响应内容,打包为 HTTP 响应报文返回,包含:
- 响应行(如:HTTP/1.1 200 OK)
- 响应头(如:Content-Type, Cache-Control)
- 响应体(HTML、JSON、文件流等)
6. 浏览器接收响应并开始渲染(全面详解)
浏览器收到 HTML 响应内容后,并不是等整个 HTML 加载完成才开始渲染,而是边解析边渲染(Streaming Rendering) 。整个过程是一个"流水线"式的异步多阶段操作,主要包含以下几个关键步骤:
🔹 1. HTML 解析 → 构建 DOM 树
浏览器使用 HTML parser 从响应体中逐字符读取内容,同时构建 DOM 树(Document Object Model)。
-
HTML 是按流(stream)读取的,解析器会从 <html> 开始,依次处理元素标签、文本节点、注释等。
-
每个标签解析完成后,会创建一个对应的 DOM 节点,并插入到树中。
-
一旦解析到 <head>、<body>、<div> 等结构,就会开始同步生成节点
但这个过程中也会遇到"中断":
- 阻塞型资源 :如未加 defer/async 的 <script> 标签,会阻塞 HTML 解析器,直到脚本下载并执行完成(因为脚本可能修改 DOM)。
🔹 2. 加载 CSS:构建 CSSOM(CSS Object Model)
CSS 是决定"怎么显示"的核心。浏览器会:
-
遇到 <link rel="stylesheet"> 或 <style> 标签时,启动下载并解析 CSS。
-
构建 CSSOM 树,代表所有样式规则。
⚠️ 注意:渲染流程需要"DOM + CSSOM"才能生成渲染树,缺一不可。因此浏览器会等待 CSS 加载完成后再开始渲染(这是为什么 CSS 会阻塞渲染)。
🔹 3. 构建渲染树(Render Tree)
渲染树 = DOM 树中可见元素 + 计算后的样式。
-
浏览器会剔除不可见元素(如 display: none)。
-
每个节点会关联一份经过样式计算后的视觉信息。
此树并不是实际页面内容,而是一个"准备渲染"的内部结构。
🔹 4. 布局(Layout / Reflow)
浏览器计算每个渲染树节点的几何信息:
-
元素在页面上的位置(top/left)
-
宽高(width/height)
-
子元素相对于父元素的排布
这个过程非常耗性能,一旦触发 Reflow,整个页面或部分节点需要重新计算位置。
🔹 5. 分层(Layering)
浏览器将渲染树划分为多个图层:
-
有 position: fixed/absolute、transform、will-change、3D 属性的元素通常会被提升为独立层。
-
分层有助于并行处理复杂动画和滚动。
这一步为后续的绘制和合成做准备。
🔹 6. 绘制(RePaint)
浏览器将每个图层中的节点绘制成位图(bitmap):
-
绘制文本、背景、边框、阴影等视觉内容
-
绘制是 GPU/CPU 协作完成的,现代浏览器会尽可能使用 GPU 加速
这个阶段决定了用户实际看到的内容长什么样。
🔹 7. 合成(Compositing)
所有图层绘制完成后,浏览器会执行合成操作:
- 将各个层叠加、排序(按 z-index)
- 生成最终的帧(Frame)发送给 GPU
- 由 GPU 输出到显示器上,用户眼中看到完整页面
🔹 8. 异步资源加载(JS/CSS/Image)
以上流程完成的是首屏渲染。但很多资源仍在继续加载中:
-
懒加载图片(lazyload)
-
JS 异步模块(import() / chunk)
-
CSS 按需加载
-
fetch / AJAX 请求
-
WebFont 加载
这些资源加载完成后,可能再次触发重排(Reflow)或重绘(Repaint) ,对用户体验产生影响。
🔹 9. JavaScript 执行:页面逻辑接管
脚本执行会影响 DOM / 样式 / 渲染:
-
操作 DOM → 可能触发布局或重绘
-
修改样式 → 可触发合成或绘制
-
动画(如 requestAnimationFrame)→ 参与帧渲染
-
定时器、事件绑定 → 驱动页面交互
前端工程师在这一阶段承担主导责任,控制用户行为和视图状态。
✅ 性能提示:什么会阻塞渲染?
- <script> 未加 defer/async
- CSS 加载未完成
- Web Font 加载迟缓(FOIT 问题)
- 同步 XHR(已过时)
- 大量 DOM 元素/深层嵌套
- 大图未懒加载
- 滥用 CSS 特性(如 box-shadow、filter)
✅ Chrome DevTools 调试建议
你可以使用 Chrome 的 Performance 面板,分析页面渲染流程:
-
Loading:资源加载时长
-
Scripting:JS 执行耗时
-
Rendering / Painting:重排重绘时间
-
Compositing:图层合成
这是判断首屏性能瓶颈的重要手段。
7. 子资源加载与异步请求
页面主框架加载完成后,浏览器会继续加载其他资源:
-
图片、字体、音视频等静态资源
-
CSS/JS 文件
-
异步接口请求(如 AJAX、fetch)
每一个资源的加载也会重复 DNS → TCP → HTTP 的过程(但通常走缓存或复用连接)。
8. 用户可见页面,交互开启
至此,页面已经呈现给用户,交互逻辑开始工作,用户可以点击、输入、滑动......而前端的责任才刚刚开始。
小结:一条 URL,牵一发而动全身
我们常说性能优化、首屏加载、前后端协作,这些都建立在对整个页面加载链条的理解之上。只有清楚每个环节发生了什么,才能有的放矢地优化体验。
所以,下次你在调试加载慢的页面时,不妨回想一下这条 URL 的"奇幻旅程"------也许问题就藏在 DNS 缓存、TCP 连接或是某个阻塞的 JS 文件中。
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏或留言讨论。下次我们可以继续聊聊从性能优化角度,怎么一步步提速这趟"旅程"。