Appearance
前言:这两天看了一些关于浏览器渲染的一些资料,趁这会有时间写一下总结
回归主题
浏览器是怎么渲染页面的呢?
天才第一步:DNS 解析
从我们输入地址按下回车开始,浏览器就开始工作
每个网址都对应有 IP 地址,那么多的网址,那由谁去保存网址和 IP 的映射关系呢?
比如百度对应的就是:202.108.22.5
没错,就是 DNS 服务器
DNS 去解析域名的时候会经历一下几个步骤:
- 浏览器缓存
这是第一步,浏览器会先检查自身缓存中有没有这个域名对应的 IP 地址,如果存在了就结束域名解析。如果没有就前往下一步
- 本地 hosts 文件
接着就会去找电脑操作系统缓存中有没有对应的解析结果。一般是 hosts 文件,如果你在该文件设置某个域名对应的 IP 地址,那么浏览器首先会跳转到这个 IP 地址。所谓的域名劫持就是通过这个原理,修改用户本地的 hosts 文件,将域名指定到特定的 IP 地址。
- 本地 DNS 解析器缓存
当上面两步都没有命中域名【翻译成白话就是:没有解析域名】的话,就会到本地 DNS 服务器进行解析,本地 DNS 服务器也会先从缓存中进行查找,大部分的域名都会从这得到解析
什么叫做本地服务器? 本地服务器(LDNS:Local DNS Serve),DNS 服务是有很多级的,所以更靠近用户的那级服务器就叫做本地 DNS 服务器.一般在你的城市的某个角落,距离你不会很远,并且这台服务器的性能都很好,一般都会缓存域名解析结果,大约 80%的域名解析到这里就完成了.
从这开始分割,上面是递归查询,往下是迭代查询。
递归查询: 客户端像本地 DNS 发起请求,本地 DNS 没有找到对应 IP 就会像其它 DNS 发起请求,找到结果再返回给客户端,而不是客户端自己去请求其它 DNS。举例:老板要秘书去找个好一点的酒店,秘书是刚来的,人生地不熟,于是秘书让司机小王去找,小王找到了告诉秘书,秘书在告诉老板,这叫递归查询
迭代查询: 本地 DNS 服务器没有找到域名对应的 IP,就会向根域名服务器发起请求,根域名服务器会告诉本地 DNS 服务器返回所查询域的主域名服务器地址,接下来一步一步的由本地 DNS 服务器去查询。举例:小王去找酒店的时候非常辛苦,他询问自己的朋友小李,小李告诉他小明知道,小王又去找小明,小明跟他说小张知道,小明又去找小张,最后终于找到了,这叫迭代查询
- 本地 DNS 服务器
1、如果本地服务器的缓存中仍然没有命中,那本地服务器就会向跟服务器(Root DNS Serve)发起请求。
2、根域名服务器根据本地服务器的请求,返回所查询域的主域名服务器(gTLD Serve)地址
3、本地服务器会向上一步返回的主域名服务器发送请求
4、主域名服务器会返回该域名对应的 Name Serve 地址
Name Serve? 就是网站注册的域名服务器
5、域名服务器根据映射关系,找到域名对应的 IP 地址,返回给本地服务器
6、本地服务器缓存这个域名对应的 IP
7、本地服务器把 IP 地址返回给用户,用户根据 TTL 值缓存到本地系统缓存中,到此域名解析结束
那么上面说的 TTL 值是什么东西? TTL(Time To Live):域名解析信息在 DNS 中的存在时间。
举个栗子 🌰 我申请了一个域名:www.lander.com 当用户访问这个地址的时候,本地服务器发现自己没有这个域名对应的 IP,那他就会向跟服务器发送请求,根域名服务器通过上述的 12345 个步骤,终于知道了这个域名对应的 IP 为 6.6.6.6,然后通过上述的 67 两个步骤,告诉给了用户。而就在这时候,本地服务器为了下次能快速找到这个域名,就把这个 6.6.6.6 保存了一段时间,这个时间就叫做 TTL。 当用户再次输入这个www.lander.com 这个地址,本地 DNS 就直接返回 6.6.6.6 给用户,直到 TTL 值过期。
建立 TCP 连接(三次握手)
- 第一次:客户端 ----> 服务端
建立连接,客户端向服务端发送 SYN 请求报文
- 第二次:客户端 <---- 服务端
服务端接受并处理 SYN 请求报文,再通过 SYN 和 ACK 报文发送给客户端
- 第三次:客户端 ----> 服务端
客户端接收 SYN 和 ACK 报文,并向服务端发送 ACK 报文
到此连接建立成功,证明客户端可以向服务端发送请求
发送 http 请求
浏览器向服务器发送请求,服务器会解析请求头,如果请求有缓存相关的信息,就会验证缓存的信息是否有效,若无效则重新返回资源,状态码 200;如果有效则返回缓存的资源,状态码 304
关闭连接(四次挥手)
写在前面:服务端也可以先向客户端发起关闭请求,我这里是以客户端为例
- 第一次:客户端 ----> 服务端
客户端向服务端发送 FIN 报文,此时客户端的状态码为 FIN_WAIT_1
- 第二次:客户端 <---- 服务端
服务端接收 FIN 报文,向客户端发送 ACK 报文,此时客户端状态码为 FIN_WAIT_2,服务端告诉客户端:我同意了你的关闭请求
- 第三次:客户端 <---- 服务端
服务端向客户端发送 FIN 报文,请求关闭连接。此时服务端状态码:LAST_ACK
- 第四次:客户端 ----> 服务端
客户端接受 FIN 报文之后,向服务端发送 ACK 报文,此时客户端状态码:TIME_WAIT。服务端接收完 ACK 报文就关闭了连接。客户端在等待 2MSL 之后如果服务端未回复,客户端就关闭连接。
什么叫 MSL? MSL:TCP 报文在网络中最长存活时间。
浏览器渲染阶段
当浏览器拿到 html 文件之后,按照渲染的时间顺序,分为下面几个阶段。
渲染进程将 HTML 转化成为了 DOM 树结构。
渲染引擎将 CSS 样式表转化为浏览器能够读懂的 styleSheets,并在这一步计算 DOM 节点的样式。
构建布局树,计算每个元素的布局信息。
对布局进行分层(图层的意思),生成分层树。
每个图层绘制列表,并将其提交到合成线程,合成线程将图层分图块,将其转化为位图。
渲染完成之后,如果 JS 操作了 DOM 节点,会根据操作的幅度面对页面进行重绘或者重排。
合成线程发送绘制图块命令给浏览器进程,浏览器进程根据指令生成页面,并显示到显示器。
构建 DOM 树
在这一步,HTML 解析器会将原始的字节数据转化为文件指定编码的字符,然后根据 HTML 规范将这些字符转化为标签,最终解析成为了一个树状的对象模型,就是 DOM 树。具体是通过以下四个步骤,每个步骤都会通过【特定的类】去处理数据
1、转码。Bytes --> Characters【HTMLTokenizer】
2、解析。Characters --> Tokens(标签)【XSSAuditor】
3、构建节点。Tokens --> Nodes【HTMLDocumentParser、HTMLTreeBuilder】
4、创建 DOM 树。Nodes --> DOM Tree【HTMLConstructionSite】
当 DOM 树构建完成,Webkit 触发 DOMContentLoaded 事件,当所有资源加载完成会触发 onload 事件
构建 CSSOM 树
CSSOM 树与 DOM 树的构建可谓是大同小异。也是通过四个步骤,只是最后一步生成的是 CSSOM 树。
在写代码过程中,有时候会遇到这种问题,控制台说 JS 报错了,页面直接白屏了,这个问题的原因就在于此:在构建 DOM 树、CSSOM 树的时候,如果有需要执行的 JS 代码,那就会阻塞 DOM 树和 CSSOM 树的构建流程,浏览器会优先执行 JS 代码,它会把这段代码交给【HTMLScriptRunner】这个类去处理,利用 JS 引擎来执行 JS 代码,当执行完毕,继续渲染 DOM 树和 CSSOM 树。
这也就是为什么提倡把 script 标签放到 body 后面或者给 script 标签加上 defer 或者 async 属性
构建渲染树(Render Tree)
渲染树(Render Tree)是由 DOM 树和 CSSOM 树合并而成,但这并不意味着,要等待 DOM 树和 CSSOM 树构建完成才开始合并,它们三者没有先后条件,也不是相对独立,而是会有交叉,并行构建,因此会形成一边加载、一边解析、一边渲染的情况。
这一步包括页面布局(排除 script、meta 等功能性、非视觉标签,排除 display:none 节点,计算元素信息,确定元素位置,构建一颗包含可见位置的布局树),页面分层(页面上有一些滚动,定位,3D 等效果,为了更方便的实现这些效果,浏览器会专门为其生成一颗分层树 LayerTree,如图 1 图 2 所示。就像一座大楼一样),栅格化(合成线程会按照视口附近的图【优先】生成位图,所谓的栅格化就是将图块转化成完位图 )。
图 1
图 2
合成线程发送绘制图块命令给浏览器进程。浏览器进程根据指令生成页面,并显示到显示器上,渲染过程完成
参考资料: