一.浏览器是如何渲染页面的
1.渲染时间点
当浏览器的网络线程在收到html文档时会生成一个渲染任务,并将其放在渲染主线程的任务队列中,在事件循环机制的作用下,将渲染任务取出放进渲染主线程开始渲染
2.具体渲染任务
(1)解析html,构建dom树
这一步会生成dom树和cssom树,在构建dom树时浏览器会将请求回来的数据解压,随后HTML解析器将其中的html字节流通过分词器拆分为⼀个个token,然后⽣成节点node,最后解析成浏览器识别的DOM树结构。 css解析器将CSS转换为浏览器能识别的styleSheets也就是cssom,可以通过控制台输⼊document.styleSheets查看
在⾸次解析HTML时渲染进程会开启⼀个预解析线程,遇到HTML⽂档中内嵌的js和CSS外部引⽤就会同步提前下载这些⽂件,下载时间以最后下载完的⽂件为准
因此css不会阻塞html解析:如果主线程解析到link的位置而此时外部的css文件还未下载解析好,主线程会继续往后执行解析后续的html,因为下载解析css是在预解析线程中发生的
而虽然预解析线程会预解析一部分的js,但是如果主线程解析到script位置,会停止解析html,转而等待js代码下载完毕,将js代码执行完成后才会继续解析html,因为js的可能代码会修改当前的dom树(可能对页面进行修改、添加事件监听器、发起网络请求等操作),所以dom树的生成要暂时暂停,因此js会阻塞html的解析
(2)样式计算
主线程遍历得到的dom树,依次算出树中每个节点的最终样式,这里遵循css的样式继承和层叠,在computed调试中可以查看,并且这个最终样式会转换样式表中的属性值,使其标准化,比如em,rem会变为px,color会以rgb()来表示
(3)布局
布局阶段会一次遍历dom树的每个节点,计算每个节点的几何信息,例如节点的宽高和设置百分比时计算节点相对包含块的位置等,去掉所有隐藏节点(在浏览器源码中设置为display:none的节点),只在布局树(layout tree)中保留可见节点
因此,往往dom树和布局树不是完全相同的,设置为display:none的元素会出现在dom树但不会出现在布局树中,::after/before这类的元素虽然不会出现在dom树中但会出现在布局树中,因为他们具有几何信息,还有一些元素间生成的匿名块/行盒会在布局树中出现
(4)分层
对布局树进行分层并生成分层树,分层树中的节点都直接或间接的属于一个图层。分层可以在浏览器调试中的layer(图层)选项查看,分层的好处是当将来某一个层改变时只会对这个层进行后续处理,提升效率,但是分层太多也会造成占用内存过大,导致卡顿,will-change,3d变换等属性可以影响分层
(5)绘制
每个图层生成一个绘制指令并将其提交到合成线程
到此为止渲染主线程的任务结束了
(6)分块
在完成绘制后主线程将每个涂层的绘制信息提交到合成线程,剩余工作由合成线程完成
合成线程先对每个图层进行分块,它会从线程池中取出多个线程来完成分块工作,将其划分为更多的小块,并且优先绘制靠近视口区域的图块,这样可以加快页面的显示速度
(7)光栅化
在光栅化线程池中将块图转化为位图,这个过程会借助GPU进程来加快速度,光栅化也会优先处理靠近视口区域的块
(8)画出界面
合成线程拿到每个层和块的位图之后,生成一个绘制图块的指令quad,指令信息会表示出每个图块应该画到页面的哪个位置,同时会处理旋转放缩等变形,因此transform的效率较高,就是因为它发生在合成线程,与渲染主线程无关。最终把quad提交给gpu进程,由gpu进程把像素点绘制在页面上
其他
- reflow(页面重排)
通过js或css修改元素的几何信息,dom或cssom可能会被修改,所以需要重新计算布局树,以及重新进行后面一系列的操作,为了避免多次操作导致布局树反复计算,浏览器会合并这些操作,当js代码全部执行完了再统一计算,因此reflow重排是一个异步操作。但是需要注意的是当获取元素的几何信息的时候会立即执行reflow
- repaint(重绘)
当可见样式改动后就会触发重绘,就会执行绘制以及其之后的阶段,并且reflow一定会导致repaint(因为元素的布局信息也属于可见样式)
二.浏览器输入URL并按下回车后
页面导航+渲染(上面写的那些)
(1)URL解析
浏览器检查URL,组装协议,构成完整的URL,然后通过浏览器的进程间通信把url请求发送到网络进程
ps.url一般包含以下部分
- host:主机域名或ip地址
- port:端口号
- path:目录路径
- query:查询参数,?开头 &相隔
- protocol:协议头,如http
- fragment:片段标识符,#后hash值,用来定位位置
www.example.com:8080/path/to/res…
- 协议是
https:// - 主机是
www.example.com - 端口是
8080 - 路径是
/path/to/resource - 查询参数是
param1=value1¶m2=value2 - 片段标识符是
section1
(2)查找缓存
浏览器在接收到url请求后查找本地是否缓存了该请求的资源
浏览器在发送请求前,根据请求头中expires和cache-control判断是否满足缓存策略,如果满足则直接从缓存中取出资源,不会发送请求,如果不满足条件,则根据If-Modified Since(last_modified)和If-None-Match(ETag)判断是否满足,满足获取资源,再不满足就向服务端获取
ps.这里对缓存做一些阐述
缓存策略是指浏览器或服务器决定如何缓存和重用资源的规则和机制。正确的缓存策略可以提高网站的性能和用户体验,减少网络流量和服务器负载,同时确保及时获取更新的资源。常见的缓存策略包括以下几种:
-
强缓存(强制缓存) :
- 通过设置
Cache-Control和Expires等响应头来控制资源的缓存时间。 Cache-Control: max-age=<seconds>:指定资源在客户端的缓存有效时间,单位为秒。Expires:指定资源的到期时间,即资源在客户端的缓存有效期。- 当客户端请求资源时,如果资源仍在有效期内(未过期),则直接从缓存中获取资源,不发送请求到服务器。
- 通过设置
-
协商缓存:
- 通过设置
Last-Modified和ETag等响应头来控制资源的缓存验证。 Last-Modified:指定资源的最后修改时间。ETag:指定资源的实体标识符,通常是资源内容的哈希值。- 当客户端请求资源时,客户端会发送
If-Modified-Since或If-None-Match等条件请求头到服务器,如果资源未发生改变,则服务器返回304 Not Modified状态码,客户端可以直接使用缓存;如果资源已发生改变,则返回新的资源内容。
- 通过设置
-
缓存位置:
- 浏览器缓存(Browser Cache):缓存存储在用户的浏览器中,例如内存缓存和磁盘缓存。
- 代理服务器缓存(Proxy Cache):缓存存储在网络上的代理服务器中,例如 CDN(内容分发网络)等。
-
缓存控制:
no-cache:表示客户端缓存的内容不过期,每次都要与服务器验证是否有新版本。no-store:表示不缓存资源,每次都要从服务器获取最新的资源must-revalidate:表示客户端必须与服务器进行缓存验证,以确保缓存的有效性。
-
缓存逻辑:
- 先检查强缓存,如果资源在有效期内,则直接使用缓存。
- 如果强缓存失效,再通过协商缓存与服务器验证资源是否已修改。
- 如果资源未修改,则使用缓存,否则获取新的资源
(3)DNS域名解析
ps.域名系统DNS:域名系统是互联网的一项服务,是一个将域名和ip地址相互映射的分布式数据库 进行DNS解析先查找缓存,没有再使用DNS服务器解析 查找顺序为:浏览器缓存>本地>hosts>路由器>isp DSN
DNS递归归查询(本地DNS服务器 -> 权限DNS服务器 -> 顶级DNS服务器 -> 13台根 DNS服务器)
(4)TCP三次握手
通过三次握手来建立TCP连接
- 第一次:客户端向服务端发一个同步数据包,标志为SYN为1(表示该标志位有效),表示这是一个请求建立连接的数据包,设置
序号Seq=a,a为传输数据的一个字节的序号,然后进入SYN-SENT状态(表示客户端已经发送了一个 SYN 报文并处于等待确认的状态) - 第二次:服务器收到了数据包的SYN标志位判断位建立连接的请求,返回一个数据确认包,其中标志位SYN=1,ACK=1,
序号seq=b,确认号ack=a+1(表示收到了传过来的a字节数据,并希望下一个传a+1),然后进入SYN-RCVD状态(表示服务端已经发送了同意建立连接的确认,并等待客户端的确认) - 第三次:客户端收到后再给服务端发送一个确认数据包,标志位ACK=1,
序号seq=a+1,确认号ack=b+1,进入ESTABLISHED状态(已建立完毕)
(5)发送http请求
在TCP连接建立成功后,浏览器会发送请求到服务器,(请求头+请求行+请求体),服务器执行后端操作,处理完之后发送一个http的响应信息,并开启http长连接,在页面关闭后TCP四次挥手断开
一些浏览器常见返回的数字
-
2xx 成功:
- 200 OK:请求成功,服务器已成功处理了请求。
- 201 Created:请求已被成功处理,并且服务器创建了新的资源。
- 204 No Content:服务器成功处理了请求,但没有返回任何内容。
-
3xx 重定向:
- 301 Moved Permanently:请求的资源已永久移动到新位置。
- 302 Found:请求的资源临时移动到新位置。
- 304 Not Modified:客户端可以使用缓存的内容。
-
4xx 客户端错误:
- 400 Bad Request:请求无效,服务器无法处理该请求。
- 401 Unauthorized:请求未经授权,需要提供身份验证信息。
- 404 Not Found:请求的资源不存在。
-
5xx 服务器错误:
- 500 Internal Server Error:服务器遇到了意外的错误,无法完成请求。
- 502 Bad Gateway:服务器作为网关或代理,从上游服务器接收到无效的响应。
- 503 Service Unavailable:服务器暂时无法处理请求,通常是由于过载或维护导致的
(6)浏览器解析渲染页面
就是一.2 里具体渲染任务那些
(7)TCP四次挥手关闭连接
当通⽤⾸部字段Conection不是Keep-Alive时,即不为TCP长连接时,开始通过四次挥⼿断开TCP连接
- 第一次:客户端主动断开连接,发送数据包给服务器,标志位FIN=1,序号位seq=c,同时停止发送数据
- 第二次:服务端收到数据包,因为还要发数据,所有无法立刻关闭,于是先返回一个数据包,其中标志位ACK=1,序号seq=d,
确认号ack=c+1 - 第三次:服务器准备好断开连接了,返回一个数据包,其中FIN=1,ACK=1,seq=e,
ack=c+1 - 第四次:客户端收到数据包后返回数据包,其中ACK=1,
seq=c+1,确认号ack=e+1
那么为什么要挥手4次呢?
通过分开第二次挥手和第三次挥手,可以避免因为双方同时发送 FIN 报文而导致的死锁情况。如果双方同时发送 FIN 报文,可能会造成双方都处于 CLOSE-WAIT 状态,无法进入最终的 CLOSED 状态,从而导致连接的无法正常关闭。