从输入URL到页面呈现知识点详解
本文对一次完整的请求和渲染过程做了一个详细的总结,小伙伴如果觉得本文对你稍微有所帮助的话,可以给笔者点个赞,有疑问的地方欢迎私聊。
# 概述
DNS
解析域名获取IP
地址- 发起
http
请求(TCP三次握手
) - 服务器处理请求,并返回请求资源
- 浏览器解析
html
文件渲染页面 - 关闭请求(
TCP四次挥手
)
# DNS 解析
说到这里就不得不提一下
DNS
这哥们的解析顺序了
浏览器缓存
- 如果有解析这个域名的记录,并且没有清除浏览器缓存就存在该域名的
IP
映射,未找到则进行下一步查找。
- 如果有解析这个域名的记录,并且没有清除浏览器缓存就存在该域名的
系统缓存
- 从本机
host
文件中查找是否存在该域名对应的IP
映射。
- 从本机
路由器缓存
- 从路由器缓存记录中查找是否存在该域名的解析记录,有则返回对应的
IP
映射。
- 从路由器缓存记录中查找是否存在该域名的解析记录,有则返回对应的
ISP(互联网服务提供商) DNS缓存
- 以上三种方式属于本地查询,如果还是未找到则进入
ISP DNS缓存
中查找。就比如你用的网络是联通的,那就进入联通的DNS缓存服务器
中查找。
- 以上三种方式属于本地查询,如果还是未找到则进入
根域名服务器
- 全球仅存在13台根域名服务器,1台主根域名服务器,12台辅根域名服务器。以上方式都未成功,则进入根域名服务器,根域名服务器收到请求后会查询区域文件记录,若不存在则将其管辖范围下的主域名服务器(如
.com
)的IP
地址发送给客户端。
- 全球仅存在13台根域名服务器,1台主根域名服务器,12台辅根域名服务器。以上方式都未成功,则进入根域名服务器,根域名服务器收到请求后会查询区域文件记录,若不存在则将其管辖范围下的主域名服务器(如
主域名服务器
- 本地
DNS服务器
向根域名服务器返回的主域名服务器发起请求,主域名服务器收到请求后,查询自己缓存,若没有则返回自己下一级域名服务器IP
地址,若还是未找到,就重复该步骤直到找到该域名对应的IP
地中。
- 本地
保存当前结果到缓存,并返回给客户端
- 客户端拿到
IP地址
请求对应资源。
- 客户端拿到
# 缓存
强缓存通过返回头的
cache-control
、expires
判断
对比缓存通过首部的
ETag
、last-modified
判断
# 强缓存
Expires是一个绝对时间,即服务器时间。浏览器检查当前时间,如果还没到失效时间就直接使用缓存文件。但是该方法存在一个问题:服务器时间与客户端时间可能不一致。因此该字段已经很少使用。
cache-control中的max-age保存一个相对时间。例如Cache-Control: max-age = 484200,表示浏览器收到文件后,缓存在484200s内均有效。 如果同时存在cache-control和Expires,浏览器总是优先使用cache-control。
指令 | 参数 | 说明 |
---|---|---|
private | 无 | 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它) |
public | 可省略 | 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存 |
no-cache | 可省略 | 缓存前必需确认其有效性 |
no-store | 无 | 不缓存请求或响应的任何内容 |
max-age=(s) | 必须 | 响应的最大值 |
Pragma
Pragma
是HTTP/1.1
之前版本遗留的通用首部字段,仅作为于HTTP/1.0
的向后兼容而使用。虽然它是一个通用首部,但是它在响应报文中时的行为没有规范,依赖于浏览器的实现。RFC
中该字段只有no-cache
一个可选值,会通知浏览器不直接使用缓存,要求向服务器发请求校验新鲜度。因为它优先级最高,当存在时一定不会命中强缓存。
如果响应报文首部的
expires
的时间大于请求的时间或者max-age
不为0并且cache-control
设置的值不为no-cache
或者no-store
,同时请求报文首部不存在Pragma
字段的时候才会命中强缓存。
# 对比缓存
last-modified
是第一次请求资源时,服务器返回的字段,表示最后一次更新的时间。下一次浏览器请求资源时就发送if-modified-since
字段。服务器用本地Last-modified
时间与if-modified-since
时间比较,如果不一致则认为缓存已过期并返回新资源给浏览器;如果时间一致则发送304
状态码,让浏览器继续使用缓存。Etag
:资源的实体标识(哈希字符串),当资源内容更新时,Etag
会改变。服务器会判断Etag
是否发生变化,如果变化则返回新资源,否则返回304。
# TCP三次握手
由上图我们可以清晰的看到TCP
三次握手的详细过程。
- 首先是由客户端发送一个建立连接的请求,
SYN
置为1(表示要建立连接),生成一个随机数seq=x
,进入SYN_SEND
状态 - 服务端接收到请求,首选确认
ack=x+1
,将SYN
置为1,ACK
置为1,也生成一个随机数seq=y
,进入SYN_RECV
; - 客户端收到请求后,发送确认包
ACK=y+1
,服务器接收到确认包,连接建立成功,客户端和服务器进入ESTABLISHED
状态。
服务器接收到客户端的http
请求后会将该http
请求封装成一个Request
对象,并通过不同的web
服务器处理,处理完结果以Response
对象返回给客户端,主要内容为状态码
、请求头
、响应报文
三个部分。
# 常见状态码
状态码 | 类别 | 原因短语 |
---|---|---|
1xx | Informational(信息性状态码) | 接受的请求正在处理 |
2xx | Success(成功状态码) | 请求正常处理完毕 |
3xx | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4xx | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5xx | Server Error(服务器错误状态码 | 服务器处理请求出错 |
# 页面渲染
# 基本流程
- 解析
HTML
文件,生成DOM
树 - 解析
CSS
文件,生成CSS
树 - 合并
DOM
树和CSS
树,生成渲染树 - 布局和绘制页面(回流,重绘)
浏览器解析HTML
文件自上而下解析遇到CSS
文件会阻塞页面渲染和JS
文件的执行,CSS
文件不会阻塞js
文件加载,他们是可以并行的,如果js
文件具有defer
(IE)或者async
属性时,该js
文件加载完就立即执行,不会受到css
加载的影响。
一旦页面
DOM
树生成接解析完毕就会触发DOMContentLoaded
(PS:IE用onreadystatechange
),就可以通过document.addEventListener('DOMContentLoaded',callback,false)
来进行绑定监听事件。
# 回流和重绘
概念
- 回流
Reflow
,又叫layout
,一般意味着DOM
元素内容、结构、位置或尺寸发生变化,需要重新计算样式和渲染树,这个过程叫做回流。
- 重绘
Repaint
,一般是因为元素一些外观上的改变(例如:背景色,边框颜色,字体颜色等),此时只要应用新样式绘制到元素上就行了,这个过程叫重绘。
所以回流比重绘的代价高的多得多,每个节点都有reflow
方法,一个节点产生回流,可能会导致子元素产生回流,甚至会导致父节点或者兄弟节点产生回流。
触发回流的一些操作
- 删除、新增
DOM
节点会触发Repaint
和Reflow
- 操作节点位置改变或给节点添加一个动画
- 改变
CSS
一些样式(如:改变节点尺寸等) - 窗口大小改变
- 修改网页默认字体
- 网页初始化的时候
js
获取精确CSS
样式的时候(如:getComputedStyle
,scroll
家族,offset
家族,client
家族等,使用这些方法将强制刷新队列)
会产生上面的问题的根源还是现代浏览器都比较聪明,他们通过队列化修改并批量执行来优化重排的过程,浏览器会将修改操作都放入一个队列当中,等到过了一段时间或者达到一个阈值才会清空队列。
那么我们为了减少回流优化页面性能又该采取什么样的措施呢?
- 在需要对一个节点采用数次操作时可以先将该元素
display:none
,修改完之后再让他显示,这样只会触发两次重排 - 需要使用js操作多个节点时可以借用
DocumentFragment
来进行批量操作后再插回页面 - 让需要被操作的节点先脱离标准文档流
position:absolute(fixed)
- 浮动
- 使用
cssText
。将需要操作的样式一次性合并然后作用到元素上。
我们说了这么多的减少重绘、重排,好像还忘了一个神奇的东西。bingo
,就是我们的CSS3 GPU加速
啦!
# CSS3 GPU加速
能触发GPU硬件加速的有如下几个属性:
transform
filter
opacity
Wall-change
他们的优点在于,在使用transform filter opacity
属性实现动画时,不会触发重排和重绘;当然,在享受GPU硬件加速带来的好处的同时呢,我们也得考虑一个问题就是如果太多元素使用这个特性,将会出现内存内存占用过大,影响性能。
# TCP四次挥手
FIN
代表断开连接标志位,ACK
确认标志位,seq
随机数,这里笔者就不多说了,类似于前面所述的三次握手
- 首先客户端向服务器端发送一个
FIN=1
+seq=u
标志位 - 服务端收到请求发送一个确认号
ACK=1
,其中ack=u+1
,seq=v
- 之后服务器再次向客户端发送一个
FIN=1
+seq=w
+ACK=1
+ack=u+1
- 客户端收到后,向服务端发送一个
ACK=1
,ack=w+1
,seq=w+1
到这里一次完整的TCP
四次挥手就完结了,大家看完这个可能会有疑问,为什么是四次挥手而不是三次,为什么FIN
和ACK
并不是一起发送的,那么这里笔者就得说道说道了。
因为刚开始是客户端主动请求断开连接,这仅仅表示客户端没有数据传给服务端了,并不代表服务端的数据也传输完了,发送一个ACK
确认标志位只是告诉客户端我知道了。
等到服务端数据也传输完了,才会出现第三次握手,也就是服务端主动发送一个FIN+ACK
给客户端,告诉客户端我的数据也发送完了,我们分手吧,然后客户端收到了,再告诉服务端我知道了。至此,分手完毕。
笔者一次性巴拉巴拉这么多,可能大家会有点不好消化,可是笔者的初衷还是希望能够遇到一个知识点就解决它并延申一些相关知识点,这样比零散的查找会好得多。
参考文章