js - 浏览器的工作原理及其优化
从浏览器输入 URL 开始,会经过如下的步骤:
- 解析 URL。
- 判断缓存。
- DNS 解析。
- TCP 三次握手
- (op)HTTPS 握手。
- 关键渲染路径。
- TCP 四次挥手。
# 解析 URL
首先会对 URL 进行解析,分析所需要使用的传输协议和请求的资源的路径。如果输入的 URL 中的协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎。如果没有问题,浏览器会检查 URL 中是否出现了非法字符,如果存在非法字符,则对非法字符进行转义后再进行下一过程。
# 缓存判断
浏览器会尝试从缓存数据库中读取数据
- 数据没有过期:返回给客户端。
- 数据过期,或者服务器返回数据的时候设置
cache-control: no-cache
:与服务器协商缓存。
# DNS 解析
DNS 占用 53 号端口,在执行 DNS 解析的时候采用的是 UDP 协议。
- 检查浏览器 DNS 缓存。
- 检查本地 DNS 缓存。
- 迭代查询(递归查询):根 -> 顶级 -> 权威。
# TCP 三次握手
TCP 建立连接的三次握手的过程,首先客户端向服务器发送一个 SYN 连接请求报文段和一个随机序号,服务端接收到请求后向服务器端发送一个 SYN ACK 报文段,确认连接请求,并且也向客户端发送一个随机序号。客户端接收服务器的确认应答后,进入连接建立的状态,同时向服务器也发送一个 ACK 确认报文段,服务器端接收到确认后,也进入连接建立状态,此时双方的连接就建立起来了。
# HTTPS 握手
如果使用的是 HTTPS 协议,在通信前还存在 TLS 的一个四次握手的过程。首先由客户端向服务器端发送使用的协议的版本号、一个随机数和可以使用的加密方法。服务器端收到后,确认加密的方法,也向客户端发送一个随机数和自己的数字证书。客户端收到后,首先检查数字证书是否有效,如果有效,则再生成一个随机数,并使用证书中的公钥对随机数加密,然后发送给服务器端,并且还会提供一个前面所有内容的 hash 值供服务器端检验。服务器端接收后,使用自己的私钥对数据解密,同时向客户端发送一个前面所有内容的 hash 值供客户端检验。这个时候双方都有了三个随机数,按照之前所约定的加密方法,使用这三个随机数生成一把秘钥,以后双方通信前,就使用这个秘钥对数据进行加密后再传输。
# 关键渲染路径
# TCP 四次挥手
最后一步是 TCP 断开连接的四次挥手过程。若客户端认为数据发送完成,则它需要向服务端发送连接释放请求。服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连接已经释放,不再接收客户端发的数据了。但是因为 TCP 连接是双向的,所以服务端仍旧可以发送数据给客户端。服务端如果此时还有没发完的数据会继续发送,完毕后会向客户端发送连接释放请求,然后服务端便进入 LAST-ACK 状态。客户端收到释放请求后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。
# 优化
# DNS 解析
DNS 预解析
DNS 预解析是浏览器试图在用户访问链接之前解析域名,这是计算机的正常 DNS 解析机制。 域名解析后,如果用户确实访问该域名,那么 DNS 解析时间将不会有延迟。 最明显的例子,DNS 预解析在某个页面中包含非常多的域名非常有效,如搜索结果页。 遇到网页中的超链接,DNS prefetching 从中提取域名并将其解析为 IP 地址,这些工作在用户浏览网页时,使用最少的 CPU 和网络在后台进行解析。 当用户点击这些已经预解析的域名,可以平均减少 200 毫秒耗时(假设用户最近还未访问过该域名),更重要的是用户不会遇到 DNS 解析最坏的情况(往往超过 1 秒)。
用法:
//用meta信息来告知浏览器, 当前页面要做DNS预解析
<meta http-equiv="x-dns-prefetch-control" content="on" />
在页面header中使用link标签来强制对DNS预解析:
<link rel="dns-prefetch" href="//www.zhix.net" />
2
3
4
注意事项:多页面重复使用 DNS 预解析会增加 DNS 解析的次数。
# 连接请求的优化
# 开源
- 增加连接请求。由于浏览器对同一域名有请求限制,可以通过配置多域名 CND 服务器,实现高并发。
- 使用像
Promise.all
和Promise.race
提高并发量。
# 节流
减少不必要的请求:
- 对高触发事件使用节流防抖。
- 使用 Promise catch,缓存请求。
# 关键渲染路径优化
- 标签语义化。在解析 HTML 的时候,浏览器会对标签做语义化处理。如果存在错误的标签的话,浏览器
会对错误的标签进行修正。(现代 IDE 和 eslint 已经可以检测标签是否错误了,但是如果采用的是字符串的方式去操作结点,可能会发生错误。如:
div.innerHTML = '<div></di>'
) - 减少不必要的重绘和重排。从 webkit 的渲染流程上看,发生重绘的时候,会发生 layout 布局的改变。而如果是重排的话,其影响要相对小一点。
- 异步加载
script
。这是由于 js 代码可能会修改 DOM,浏览器为了确保渲染出正确的结果,会先 执行 js 代码,停止对 HTML 解析。