本文主要两个目的:第一,将前面涉及网络协议特别是 TCP 协议和 HTTP 协议、DNS 解析等等知识点贯穿;第二,在此基础上介绍浏览器渲染过程,以及为 Web 优化提供基础。
flowchart TB
A[地址栏输入 URL] --> B[DNS 域名解析 IP<br>缓存 → LDNS → 迭代查询]
B --> C[建立 TCP 连接<br>三次握手]
C --> D[发送 HTTP 请求]
D --> E[服务器处理请求]
E --> F[返回 HTTP 响应结果]
F --> G[关闭 TCP 连接<br>四次挥手]
G --> H[浏览器加载 解析和渲染]
地址栏输入 URL
URL:Uniform / Universal Resource Locator,即统一资源定位符,它实际上就是网站网址,浏览器就是靠 URL 来查找资源位置。可以把 URL 分割成几个部分:协议、网络地址、资源路径。
- 传送协议:URL 包含协议部分,是浏览器和 www 万维网之间的沟通方式,它会告诉浏览器正确在网路上找到资源位置。最常见的网络传输协议的是 HTTP 协议(超文本传输协议)(https 则是进行加密的网络传输);其他也还有 ftp、file、https、mailto、git 等,还有自定义的协议(私有协议),例如 tencent。不同协议有不同的通讯内容格式。
- 网络地址:指示该连接网络上哪一台计算机,可以是域名或者 IP 地址,可以包括端口号。
- 资源路径:指示从服务器上获取哪一项资源。
例如 http://www.quaro.com/question/123456/:
- 协议部分:http
- 网络地址:www.quaro.com
- 资源路径:/question/123456/
DNS 域名解析 IP
这里主要贯穿下思路,DNS 涉及相关其它知识点可以参考:DNS 相关详解。
基础概念
- IP 地址:IP 协议为互联网上的每一个网络和每一台主机分配的一个逻辑地址。IP 地址如同门牌号码,通过 IP 地址才能确定一台主机位置。服务器本质也是一台主机,想要访问某个服务器,必须先知道它的 IP 地址。
- 域名 DN(domain name):域名是为了识别主机名称和组织机构名称的一种具有分层的名称。IP 地址由四个数字组成,中间用点号连接,在使用过程中难记忆且易输错,所以用我们熟悉的字母和数字组合来代替纯数字的 IP 地址,比如我们只会记住 www.baidu.com(百度域名)而不是 220.181.112.244(百度的其中一个 IP 地址)。
- 计算机域名系统 DNS(Domain Name System or Domain Name Service):它是由域名解析器和域名服务器组成的。域名服务器是指保存有该网络中所有主机的域名和对应 IP 地址,并具有将域名转换为 IP 地址功能的服务器。每个域名都对应一个或多个提供相同服务的服务器的 IP 地址,只有知道服务器 IP 地址才能建立连接,所以需要通过 DNS 把域名解析成一个 IP 地址。
查找域名对应的 IP 地址
通过域名查找 IP 过程:浏览器缓存 -> 系统缓存 -> 本地 DNS 服务器缓存:
- 浏览器搜索自己的 DNS 缓存(维护一张域名与 IP 地址对应表);
- 搜索操作系统中的 DNS 缓存(维护一张域名与 IP 地址对应表);
- 搜索操作系统的 hosts 文件(windows 环境下,维护一张域名与 IP 地址对应表);
- 操作系统将域名发送到本地区域服务器(LDNS),进行查找,成功则返回结果(递归查询),失败则发起一个迭代 DNS 请求(迭代查询);
- 本地域名服务器 LDNS 将得到的 IP 地址返回给操作系统,同时也将 IP 地址缓存起来;
- 操作系统将 IP 地址返回给浏览器,同时将 IP 地址缓存起来。
DNS 迭代查询和递归查询
这里独立一节只是补充强调说明这两个重要的概念,实际上是结合在一起的:
- 递归查询:客户端与服务器之间属于递归查询,即当客户机向 DNS 服务器发出请求后,若 DNS 服务器本身不能解析,会向另一个 DNS 服务器发出查询请求,最后将结果转交给客户端的过程。服务器必须回答目标 IP 与域名的映射关系。
- 迭代查询:DNS 服务器之间属于迭代查询。服务器接收到一次迭代查询回复一次结果,这个结果不一定是目标 IP 与域名的映射关系,也可以是其他 DNS 服务器的地址。
请求和响应数据
包括:TCP 连接建立、发送 HTTP 请求、服务端处理、返回 HTTP 结果、TCP 连接关闭。
建立 TCP 连接(3 次握手)
上一步找到 IP 之后,便可以开始建立 TCP 连接了,这里就是我们所说的 TCP 3 次握手。详情可以参考:TCP 协议详解。
发送 HTTP 请求
与服务器建立了连接后,就可以向服务器发起请求了。详情可以参考:HTTP 协议详解。请求报文由请求行、请求首部字段、空行以及报文主体组成。
服务器处理请求
服务器端收到请求后的由 web 服务器(准确说应该是 http 服务器)处理请求,诸如 Apache、Ngnix、IIS 等。web 服务器解析用户请求,知道了需要调度哪些资源文件,再通过相应的这些资源文件处理用户请求和参数,并调用数据库信息,最后将结果通过 web 服务器返回给浏览器客户端。
返回 HTTP 响应结果
服务器处理完请求后,就会发送响应结果。响应报文由状态行、响应首部字段、空行以及报文主体组成。响应结果中会有对应的 HTTP 状态码,可分为 5 类(1XX 信息、2XX 成功、3XX 重定向、4XX 客户端错误、5XX 服务器错误)。详情可以参考:HTTP 协议详解。
关闭 TCP 连接(4 次挥手)
为了避免服务器与客户端双方的资源占用和损耗,当双方没有请求或响应传递时,任意一方都可以发起关闭请求。与创建 TCP 连接的 3 次握手类似,关闭 TCP 连接,需要 4 次握手。详情可以参考:TCP 协议详解。
浏览器加载,解析和渲染
浏览器构成
浏览器内核也称渲染引擎,主要有 3 种:
| 内核 | 代表浏览器 |
|---|---|
| Trident 内核 | IE |
| Webkit 内核 | Chrome、Safari |
| Gecko 内核 | FireFox |
浏览器加载
加载过程如下:当浏览器获得一个 html 文件时,会”自上而下”加载,并在加载过程中进行解析渲染:
- 加载过程中遇到外部 css 文件,浏览器另外发出一个请求,来获取 css 文件;
- 遇到图片资源,浏览器也会另外发出一个请求,来获取图片资源。这是异步请求,并不会影响 html 文档进行加载;
- 但是当文档加载过程中遇到 js 文件,html 文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中 js 文件加载完毕,还要等待解析执行完毕,才可以恢复 html 文档的渲染线程。
加载外联 js 和 css 的阻塞情况,一个不太严谨但方便记忆的口诀:JS 全阻塞,CSS 半阻塞:
- JS 会阻塞后续 DOM 解析以及其它资源(如 CSS、JS 或图片资源)的加载;
- CSS 不阻塞 DOM 的加载和解析(它只阻塞 DOM 的渲染呈现,这里谈加载),不会阻塞其它资源(如图片)的加载,但是会阻塞后续 JS 文件的执行(原因之一是,js 执行代码可能会依赖到 css 样式,css 只阻塞执行而不阻塞 js 的加载);
- 鉴于上面的特性,当 css 后面存在 js 的时候,css 会间接地阻塞 js 后面资源的加载(css 阻塞 js,js 阻塞其他资源)。
现代浏览器会进行 prefetch 优化,浏览器在获得 html 文档之后会对页面上引用的资源进行提前下载。外联 js 文件使用 defer 属性和 async 可以达到异步非阻塞加载的效果,由于现代浏览器都存在 prefetch,所以 defer、async 可能并没有太多的用途,可以作为了解扩展知识,仅仅将脚本文件放到 body 底部(但还是在 </body> 之前)就可以起到很不错的优化效果(遵循先解析再渲染再执行 script 这个顺序)。当把 js 放在最后的时候,其实浏览器将自动忽略 </body> 标签,从而自动在最后的最后补上 </body>。
浏览器解析和渲染
步骤如下:
- 解析 html,生成 dom 树;
- 解析 css,生成 cssom 树;
- 将 dom 树和 cssom 树合并,生成渲染树;
- 遍历渲染树,开始布局和计算;
- 绘制渲染树,显示到屏幕。
1. 解析 html,生成 dom 树
当浏览器接收到服务器响应来的 HTML 文档后,会自上而下扫描文档,开始解析,遍历文档节点,生成 DOM 树。整个构建过程其实包括:字节 -> 字符 -> 令牌 -> 节点对象 -> 对象模型。下面是示例代码:
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
2. 解析 css,生成 cssom 树
每个 css 文件都被分析成一个 stylesheet 对象,每个对象都包含 CSS 规则。css 规则对象包含对应于 css 语法的选择器和声明对象以及其他对象。构建过程没有什么特别的差别,下面是示例代码:
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
3. 将 dom 树和 cssom 树合并,生成渲染树
浏览器会先从 dom 树的根节点开始遍历每个可见节点,找到其适配的 CSS 样式规则并应用。将 dom 树与 cssom 树结合在一起,这就是渲染树。
- 每一个渲染对象都对应着 dom 节点,但是非视觉(隐藏,不占位)dom 元素不会插入渲染树,如
<head>元素或声明display: none;的元素; - 渲染对象与 dom 节点不是简单的一对一的关系:一个 dom 可以对应一个渲染对象,但一个 dom 元素也可能对应多个渲染对象,因为有很多元素不止包含一个 css 盒子(如当文本被折行时,会产生多个行盒,这些行会生成多个渲染对象;又如行内元素同时包含块元素和行内元素,则会创建一个匿名块级盒包含内部行内元素,此时一个 dom 对应多个渲染对象)。
4. 遍历渲染树,开始布局和计算
布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置。布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小,所有相对的测量值也都会被转换为屏幕内的绝对像素值。
5. 绘制渲染树,显示到屏幕
在绘制阶段,浏览器会立即发出 Paint Setup 与 Paint 事件,开始将渲染树绘制成像素,绘制所需的时间跟 CSS 样式的复杂度成正比,绘制完成后,用户就可以看到页面的最终呈现效果了。
Repaint 和 Reflow
当用户在浏览网页时进行交互或通过 js 脚本改变页面结构时,以上的部分操作有可能重复运行,此过程称为 Repaint 或 Reflow。
- Repaint:当元素改变的时候,将不会影响元素在页面当中的位置(比如 background-color、border-color、visibility),浏览器仅仅会应用新的样式重绘此元素,此过程称为 Repaint。
- Reflow:当元素改变的时候,将会影响文档内容或结构,或元素位置,此过程称为 Reflow(HTML 使用的是 flow based layout,也就是流式布局,所以,如果某元件的几何尺寸发生了变化,需要重新布局,也就叫 Reflow)。
Reflow 的成本比 Repaint 的成本高得多的多,我们应当尽量避免 Reflow。
进而:如何优化浏览器渲染过程
针对 html:
- html 文档结构层次尽量少,最好不深于 6 层;
- 首屏 html 可以少量,主体结构动态插入;
- 尽量减少将 DOM 节点属性值放在循环当中,会导致大量读写此属性值;
- 创建有效的 HTML 和 CSS,不要忘记指定文档编码,比如
<meta charset="utf-8">。
针对 css:
- 使用媒体查询,减少初次 cssom 树的构建量;
- 尽量用 id 和 class,不要过度层叠;
- 样式结构层次尽量简单;
- 尽可能地为产生动画的 HTML 元素使用 fixed 或 absolute 的 position,那么修改他们的 CSS 是不会 Reflow 的。
针对 js:
- 使用 defer 和 async,避免对文档的阻塞;
- 可以的话,动态插入 js,避免阻塞;
- 不要通过 JS 逐条修改 DOM 的样式,提前定义好 CSS 的 Class 进行操作。
针对引入位置:
- css 放到 head,让 cssom 树先行构建;js 放到
</body>前,保证 dom 树先行构建,不被阻塞; - 避免 js 文件的插入跟在 css 文件之后,避免 css 解析对 js 执行的延迟,造成阻塞。
针对资源载入:
- 对页面资源进行压缩,对传输进行 gzip 压缩;
- 利用 link 标签的 rel 属性进行预解析,运用 http 缓存。
参考文章
- https://www.zhihu.com/question/263866883/answer/276139578
- https://segmentfault.com/a/1190000008835506
- https://segmentfault.com/a/1190000013522717
- https://blog.csdn.net/qq_29311407/article/details/79988700
- https://www.cnblogs.com/yongwunaci/p/10671999.html
- https://www.jianshu.com/p/8758e8a237ee
系列导航
- 上一篇:DNS 相关详解
- 下一篇:Wireshark 介绍及抓包分析
- 相关阅读:TCP 协议详解 | HTTP 协议详解 | DNS 相关详解