前言
关于 《地址栏输入URL
到页面展现经历了哪些过程》 这个问题是一个老生常谈了,无论是在面试中,还是说工作处理问题,都会用的上,最近在研究相关的内容,所以就我的知识进行了一个梳理,后面理解深入还会补充,或者也可以在评论区留言,我会看到的哦!
简单概括性的回答:
主要是两大部分工作,一部分是网络部分,一部分是浏览器渲染部分
一、网络部分
总结概括如下:
1. 解析url并且判断其合法性
2. DNS解析url到指定的ip
3. 建立TCP连接(3次握手)
4. 发送HTTP请求(请求行/头/体)(查询http缓存阶段见下图)
5. 服务器处理收到的请求,将数据返回浏览器
6. 浏览器收到HTTP响应
7. 浏览器解析并渲染页面
8. 关闭TCP连接
关于请求的步骤如下图:
下面就上述步骤中的一些需要重点理解的点,来展开讲讲。
1. 解析url
并且判断其合法性
浏览器会先进行
url
解析,会判断输入的是一个合法的url
还是一个待搜索的关键词
不合法:如果输入的 URL
中的协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎。
合法:检验地址合法后,浏览器会检查URL
中是否出现了非法字符,如果存在非法字符,则对非法字符进行转义后再进行下一过程。
2. DNS
解析url
的过程
查找过程如下:
-
本地电脑检查浏览器缓存中有没有这个域名对应的解析过的
ip
地址,如果缓存有,解析结束 -
缓存中没有数据,查找操作系统缓存中是否有这个域名对应的解析结果
-
前两个都没有,操作系统会把这个域名发送给这个本地DNS服务器
-
本地没有,去根DNS服务器请求解析
-
根服务器返回给本地一个顶级DNS服务器地址,拿到后向顶级DNS服务器发送解析请求
-
接受请求查询并返回此域名对应的
Name Server
域名服务器的地址 -
Name Server根据映射关系表找到目标ip,返回该域名对应的
IP
和TTL
值,本地DNS
服务器会缓存一份这个域名和IP的对应关系,缓存时间由TTL值控制。 -
把解析的结果返回给本地电脑,本地电脑根据TTL值缓存在本地系统缓存中
迭代查询和递归查询的区别:
3. 关于TCP
连接的三
次握手和四
次挥手
-
从最开始双方都处于
CLOSED
关闭状态。 -
客户端主动请求建立连接,发送
SYN
到服务端 , 自己变成了SYN-SENT
状态。 -
服务端接收到请求,针对客户端的
SYN
的确认应答,返回SYN
和ACK
(对应客户端发来的SYN
),并请求建立连接,自己变成了SYN-REVD
。 -
客户端收到服务端的请求,对服务端
SYN
的确认应答,并发送ACK
给服务端,自己变成了ESTABLISHED
状态; -
服务端收到
ACK
之后,也变成了ESTABLISHED
状态。
另外需要提醒你注意的是,SYN
是需要消耗一个序列号的,下次发送对应的 ACK
序列号要加1
,因为
凡是需要对端确认的,一定消耗TCP
报文的序列号。
4. 握手为什么是三次
握手不是四次
?
三次握手的目的是确认双方发送
和接收
的能力,那四次握手可以嘛?
当然可以,100
次都可以。但为了解决问题,三次就足够了,再多用处就不大了。
5. 三次
握手过程中可以携带数据么?
首先说答案:第三次握手的时候,可以携带。前两次握手不能携带数据。
如果前两次握手能够携带数据,那么一旦有人想攻击服务器,那么他只需要在第一次握手中的 SYN
报文中放大量数据,那么服务器势必会消耗更多的时间和内存空间去处理这些数据,增大了服务器被攻击的风险。第三次握手的时候,客户端已经处于ESTABLISHED
状态,并且已经能够确认服务器的接收、发送能力正常,这个时候相对安全了,可以携带数据。
6. 挥手为什么是四次
,三次
不行吗?
答案:不可以
是因为挥手要保证双方都确认断开。所以每一步的反馈就必须干净利落,而不能像握手一样耦合在一起,例如:客户端申请断开连接后,服务端返回确认,此时只能代表客户端不会再向服务器发送信息,但不代表它不可以接收信息。所以如果双方都要断开,就要对每一步的请求进行确认,而不能耦合在一起,所以三次不可以。
7. 在发送http
请求时会先进行缓存查询,那么什么是http
缓存?
http
缓存指的是: 当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有要请求资源的副本
,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源。
常见的http
缓存只能缓存get
请求响应的资源,对于其他类型的响应则无能为力,所以后续说的请求缓存都是指get
请求
分类:
- 根据是否需要再请求:可分为强制缓存和协商缓存,强制缓存如果生效,不需要再和服务器发生交互,而协商缓存不管是否生效,都需要与服务端发生交互;
- 根据是否可以被单个或者多个用户使用来分类,可分为私有缓存和共享缓存
在强制缓存时:cache-control
的优先级要高于expirse
,原因如下:
expirse
是相对于服务器时间的,cache-control
和max-age
是相对于上次返回请求结果的时间
在协商缓存时,ETag
的优先级是高于Last-Modifined
,原因如下:
-
某些服务器不能精确得到文件的最后修改时间, 这样就无法通过最后修改时间来判断文件是否更新了。
-
某些文件的修改非常频繁,在秒以下的时间内进行修改
Last-Modified
只能精确到秒。 -
一些文件的最后修改时间改变了,但是内容并未改变。 我们不希望客户端认为这个文件修改了。
关于缓存的细节如下图:
二、浏览器渲染部分
渲染过程是指将我们的
html
css
js
的代码通过复杂运算,转换成像素展示到屏幕上。
前提是先通过浏览器的网络线程去获取
HTML
,当收到HTML
文档后,会生成一个渲染任务,交由渲染主线程的消息队列。 然后在事件机制的作用下,渲染主线程取出消息队列中的渲染任务,开启渲染流程。
渲染流程分为多个阶段:
每个阶段性任务都会有输出,作为下一步任务的输入。
总结概括:
html
解析:解析整个html
内的所有内容,输出DOM
和CSSOM
树- 样式计算:合并
DOM
和CSSOM
树,计算得到初步的样式 - 布局:将
render
树通过计算相对大小、位置等信息,得到layout
树 - 分层:在这一步将得到最终样式的树进行分层处理,方便后面有操作或更新时的最小影响
- 绘制:将已经分层的内容生成指令集,每个指令代表本层是如何进行绘制,到此渲染主线程任务结束
- 分块:将已经分层好的内容进行颗粒度再小的分块,此时交由合成线程,合成线程会开辟多个线程同时进行
- 光栅化: 在此步骤将我们已经分块好的内容交由
GPU
进行光栅化,这一步会得到一个一个的位图块儿 - 画:将我们得到的一堆位图块进行最终合并画到屏幕上
1. 解析html
和 css
先将字符串解析转换成对象结构,方便后续操作。在这一步会产生两棵树:解析
html
生成dom
树 ,解析CSS
生成CSSOM
树(包含有内部样式表、外部样式表,内联样式表,浏览器默认样式表等内容 )
什么是预解析?
浏览器在开始解析前,会启动一个预解析道线程,率先下载
HTML
中的外部CSS
文件和外部的JS
文件。预解析线程快速浏览,找到CSS
,通知网络线程去下载CSS
,下载完去交给预解析线程去解析,解析完再回给渲染主线程生成CSSOM
树。主要目的:提高整体解析效率。
CSS
会不会阻塞html
解析?
如果主线程解析到
link
的位置,此时外部的CSS
文件还没有下载解析好,主线程不会等待,继续解析后续的HTML
,当CSS
解析完成后,直接交给主线程取生成CSSOM
树即可。这是因为下载和解析CSS
的工作是在预解析线程中进行的。这就是CSS
不会阻塞HTML
解析的根本原因。
CSS
会不会阻塞渲染?
因为当
css
树没有构建完成,页面是不会渲染到浏览器的,需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间,尽量减少样式嵌套,如果时间过长会导致白屏。
CSS
优化方案
-
将css文件放在页面最上面
-
减少使用 * 通配符
-
尽量减少页面重排、重绘
-
尽量少的使用
@import
,而是使用link
,link
是同步加载 -
删除冗余代码
-
如果值为
0
,可以不写单位 -
减少嵌套不超过
3
层,可以尽量多的使用继承
js
会不会阻塞html
解析?
如果主线程解析到
script
位置,会停止解析html
,转而等待js
文件下载好,并将全局代码解析执行完成后,才能继续解析html
。这是因为js
代码的执行过程可能会修改当前的DOM
树,所以DOM
树的生成必须暂停,这就是js
会阻塞html
解析道根本原因。
js
优化方案:
-
将
js
放到页面底部,当html
解析完再去解析js
-
异步加载:设置
defer
,当遇到defer
标识会先进行下载但不执行,等html
渲染完成后再执行
图片会不会阻塞渲染?
图片不会阻塞渲染,因为浏览器有一个单独的渲染进程去渲染图片,是并行的,所以不会阻塞渲染。
2. 进行样式计算,合成render
树
主线程会遍历得到的DOM 树,结合
CSSOM
树,去计算样式,计算过程中有些样式会变化,去得到每个元素的最终样式,称之为Computed Style
。在这一过程中,很多预设值会变成绝对值,比如red
会变成red(255,0,0)
,相对单位会变成绝对单位,比如em
会变成px
。
3. 布局
布局阶段会依次遍历
DOM
树的每一个节点,计算每个节点的几何信息,例如节点的宽高、相对包含块的位置。大部分时候,DOM树和布局树并非一一对应。布局完成后会得到布局树。
Dom
树和Layout
树为什么不是一一对应的?
比如
display:none
的节点没有任何信息,因此不会生成布局树;又比如使用了伪类选择器,虽然DOM
树中不存在这些伪元素节点,但他们拥有几个信息,所以会生成到布局树中。还有匿名行盒和匿名块盒等待都会导致DOM
树和布局树无法一一对应。
4. 分层:Layer
主线程会使用一套复杂的策略对整个布局树中进行分层,每个层面可以单独进行绘制。分层的好处在于,将来某个层次改变后,仅会对该层进行后续处理,从而提升效率。像滚动条、堆叠上下文、
transform
、opacity z-index
等样式可能会影响分层结果,也可以通过设置will-change 属性更大程度的影响分层结果.
will-change:transform
一定是效率出现了问题,调整后发现是分层导致的卡顿,不要滥用,这个样式会告诉浏览器可能会发生变化
5. 绘制
会先去生成绘制的指令集合,用于描述这一层的内容如何画出来,例如:先后顺序,位置确定等等。主线程任务会停止,然后交给其他线程来操作。
6. 分块
完成绘制后,主线程将每个图层的绘制信息提交给合成线程,剩余工作交给合成线程完成。合成线程首先将每个图层进行分块,将其划分为更多的小区域,分块会将每一层分为多个小的区域,将工作交给多个线程同时进行
它会从线程池中拿取多个线程来完成工作
7. 光栅化
合成线程会将块信息交给
GPU
进程,以极高的速度完成光栅化,GPU
进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块。光栅化的结果,就是一块一块的位图
8. 画
最终画在浏览器屏幕上
三、和整个过程相关的一些扩展知识点:
1. 什么是浏览器的默认样式?
p div
为什么独占一行,是因为浏览器给它设置了默认样式,display:block
,所以它才独占一行,惊不惊喜,意不意外?原来这么简单
2. 什么是行盒?什么是块盒?
内容必须在行盒中,如果在p 元素中有一个元素,没有包行盒,那就会自动给它补充一个匿名行盒。已经早就没有行元素和块元素的说法了,因为元素的样式是由css来决定的而不是html的元素,行盒和块盒不能相邻。
3. 为什么要使用 http
缓存?
- 减少了冗余的数据传输,节省了网费。
- 缓解了服务器的压力, 大大提高了网站的性能
- 加快了客户端加载网页的速度
4. 为什么浏览器会有兼容性问题?
浏览器的兼容性?
源于浏览器的渲染引擎,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。不同的浏览器内核对网页编写语法的解析也有不同,因此同一网页在不同的内核的浏览器里的渲染(显示)效果也可能不同,这就出现了浏览器兼容性问题。
常见的浏览器内核:
-
Trident内核
:IE
、360
、搜狗浏览器等。 -
Webkit内核
:Safari
、Chrome
等。
扩展知识点:【 Chrome
的Blink
是WebKit
的分支,V8
是Blink
的js
引擎】
关于js
兼容性问题的解决
使用
babel
来转译,因为它是可以将最新版本的js 语法转为最老的js 语法,让更多的浏览器可以识别
关于css
兼容性问题的解决
在样式的头部加浏览器的前缀:例如
-webkit
关于html
兼容性问题:
可以通过设置
<!DOCTYPE html>
字段来解决,如果不写,各个浏览器会使用自己的怪异模式去解析,不是我们想要的结果,写了是让浏览器用统一方式去处理
5. 什么是重排reflow
?
reflow
的本质就是重新计算layout
树。当局部或者整体布局发生改变,例如:新增或者删除dom
元素,改变margin
、padding
、 字体大小等等。可能会影响到布局时,就会启动重新计算样式,重新生成布局,重新排列元素。相当于从计算样式向后所有的步骤都要走一遍,所以会比较耗费性能。
为了避免连续的多次操作导致布局树反复计算,浏览器会合并这些操作,当js 代码全部完成后再进行统一计算。所以,改动属性造成的reflow
是异步完成的。也同样因为如此,当js 获取布局属性时,就可能无法获取到最新的布局信息。浏览器为了避免这个情况的产生,读取样式会立即执行reflow
6. 什么是重绘 repaint
?
repaint
的本质就是重新根据分层信息计算了绘制指令。当某些元素的外观被改变,就需要重新计算。计算样式和绘制阶段之后的阶段都要重新过一遍。
重绘不一定导致重排,但重排一定会导致重绘。重排的开销要远远大于重绘,会破坏用户体验,并且让UI展示非常迟。所以我们要尽量减少页面重排次数。
7. 为什么transfrom
的效率高?
因为
transform
改动样式后,就只影响了计算样式和最后画的步骤,中间很多步骤都可以跳过,所以它的效率要高。
如果transform
来配合 animation
一起使用,效率会更高,中间步骤直接都跳过,只影响最后画的步骤。
8. 浏览器遇到无法解析的css 会做什么?
9. 关于几种刷新后对缓存的影响?
地址栏回车:
浏览器发起请求,按照正常流程,本地检查是否过期,然后服务器检查新鲜度,最后返回内容。
点击刷新按钮或者按 F5
:
浏览器直接对本地的缓存文件过期,但是会带上
If-Modifed-Since
,If-None-Match
,这就意味着服务器会对文件检查新鲜度,返回结果可能是304
,也有可能是200
。
用户按 Ctrl+F5
强制刷新:
浏览器不仅会对本地文件过期,而且不会带上
If-Modifed-Since
,If-None-Match
,相当于之前从来没有请求过,返回结果是200
。
9. 什么时候会断开TCP
的连接
不同的协议断开的标准不同,但都是一定程度上避免浪费资源,因为保持连接对于服务器来说也是需要占用资源的
-
http1
: 如果没有设置keep-alive
,一个请求发送过去,服务端响应后,就会立即断开 -
http1.1
内部自带keep-alive
,短时间内是不用重复连接的,keep-alive
会有一个过期时间,当超过设置时间会自动断开 -
http2
是支持多路复用的,所以和请求否响应无关,它的断开标准通常是当双方服务器在30秒
内如果没有再发生交互,即断开连接。
本期先分享到这里,后面有浏览器相关的内容我会再做补充,欢迎各位掘友收藏关注哈!!!
原文链接:https://juejin.cn/post/7240333779221987384 作者:玖月晴空