这是一个很经典的面试问题,根据面试者的知识水平和掌握情况,所能表达的内容广度和深度也是截然不同的。
从DNS、TCP、HTTP到浏览器运行逻辑,以及之后的渲染流程等等,它能包含的内容非常多。面试官完全可以从这里开始,然后进而逐步拓展到其他问题,如:网络相关、浏览器相关、JS与CSS相关、优化相关等等。
所以,大致了解下这个流程还是很有必要的。希望下次面试的时候,面试官问及这个问题时,你可以自信的说出这句话------你想让我说多久吧🤓?
本文为面试专题之JavaScript进阶------原理篇系列。
需要提前了解的知识点
首先这个过程是需要各个进程之间相互配合的,涉及:
- 浏览器进程:主要负责用户交互、子进程管理和文件储存等功能。
- 网络进程:是面向渲染进程和浏览器进程等提供网络下载功能。
- 渲染进程 :的主要职责是把从网络下载的
HTML
、JavaScript
、CSS
、图片等资源
解析为可以显示和交互的页面。
注:这里的网络
进程
和浏览器渲染进程中的异步http请求线程
是有区别的,关于浏览器机制细节见从宏观层面理解------浏览器中JavaScript的运行机制
用户从地址栏输入
当用户在地址栏中输入一个查询关键字时,地址栏会判断输入的关键字是否为搜索内容
- 如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的
URL
- 如果判断内容符合
URL
规则,那么地址栏会根据规则,把这段内容加上协议,合成为完整的URL
- 是web网络协议的情况
https://www.baidu.com
- 不是网络协议的情况:
file:///C:/Users/sunss/Desktop/ES6.pdf
URL 请求过程
接下来,便进入了页面资源请求过程。
这时,浏览器进程会通过进程间通信(IPC)
把 URL
请求发送至网络进程,网络进程接收到 URL
请求后,会在这里发起真正的 URL
请求流程。
- 有缓存:网络进程首先查找本地是否有缓存,如果有,则直接返回缓存数据,不再发起真正的网络请求。
- 无缓存:如果在缓存中没有查找到资源,那么直接进入网络请求流程
网络请求流程:
DNS
解析,获取请求域名的服务器IP
地址- 利用
IP
地址和服务器建立TCP
连接(如果请求协议是HTTPS
,那么还需要建立TLS
连接) - 连接建立之后,浏览器端会构建请求头等信息,并把和该域名相关的
Cookie
等数据附加到请求头中,然后向服务器发送构建的请求信息。 - 服务器接收到请求信息后,会根据请求信息生成响应数据(响应头和响应体等信息),并发给网络进程。
- 等网络进程接收了响应行和响应头之后,开始解析响应头的内容
状态码
根据返回的状态码信息,会做出对应的处理,目前主要是这5种分类:
- 1xx开头的:该类状态码属于提示信息,是协议处理中的一种中间状态,实际用到的比较少。
- 2xx开头的:该类状态码表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。
- 3xx开头的:该类状态码表示客户端请求的资源发送了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向。
- 4xx开头的:该类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。
- 5xx开头的:该类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。
本文篇幅有限,这里我们只着重介绍 3xx 类型,这里只需重点了解:重定向
(即 301、302)
3xx
301 Moved Permanently
:表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。302 Found
:表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。304 Not Modified
:不具有跳转的含义,表示资源未修改,重定向已存在的缓冲文件,也称缓存重定向,用于缓存控制。
301 和 302 都会在响应头里使用字段 Location
,指明后续要跳转的 URL,浏览器会自动重定向新的URL。
重定向之 301与302
重定向的两个基本条件:
- 响应状态码为 3xx
- 响应头中必须包含
Location
字段(该字段用于表示要重定向到的位置)
基本概念:
- 301是永久重定向
- 302是临时重定向
应用场景:
- http网站跳转到https网站
- 二级域名跳转到主域名,
http://www.abc.com
跳转到http://abc.com
- 404页面失效跳转到新的页面
- 老的域名跳转到新的域名
一些说明:
- 浏览器会缓存"301"永久重定向的页面,而不会缓存"302"临时重定向的页面。
响应数据类型处理
处理完重定向等跳转信息之后,浏览器会根据响应数据类型,进行不同的处理,如:下载、HTML页面
- Content-Type :它是 HTTP 头中一个非常重要的字段, 它告诉浏览器服务器返回的响应体数据是什么类型
- HTML 文档,如
text/html
- 文件下载,如
application/octet-stream
- HTML 文档,如
- 如果是下载类型,那么该请求会被提交给浏览器的下载管理器,同时该
URL
请求的导航流程就此结束。 - 如果是HTML类型,那么接下来开始准备渲染进程。
渲染进程准备阶段
渲染进程准备好之后,还不能立即进入文档解析状态,因为此时的文档数据还在网络进程中,并没有提交给渲染进程,所以这一步就进入了提交文档阶段。
默认情况下,Chrome 会为每个页面分配一个渲染进程(某些场景下会出现几个Tab合并在一个进程中的情况)
process-per-site-instance策略:如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点的话,那么新页面会复用父页面的渲染进程
浏览器进程通知渲染进程和网络进程建立传输数据的"管道
",传输完成之后,浏览器进程会更新浏览器界面状态(包括了安全状态、地址栏的 URL、前进后退的历史状态)
渲染阶段
浏览器渲染过程如下
- 解析HTML,生成DOM树
- 解析CSS,生成CSSOM树
- 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
- Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
- Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
- Display:将像素发送给GPU,展示在页面上。(这一步其实还有很多内容,比如会在GPU将多个合成层合并为同一个层,并展示在页面中。而css3硬件加速的原理则是新建合成层)
渲染过程详细分析
- 渲染进程将
HTML
内容转换为能够读懂的DOM 树结构。 - 渲染引擎将
CSS
样式表转化为浏览器可以理解的styleSheets
,计算出DOM
节点的样式。 - 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令(
DrawQuad
)给浏览器进程。 - 浏览器进程根据图块命令(
DrawQuad
)消息生成页面,并显示到显示器上。
基本概念解释
分层
因为页面中有很多复杂的效果,如一些复杂的 3D
变换、页面滚动,或者使用 z-indexing
做 z
轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree
)。
符合条件会创建新的层:
- 拥有层叠上下文属性的元素会被提升为单独的一层
- 需要剪裁(
clip
)的地方也会被创建为图层
绘制
渲染引擎会把一个图层的绘制拆分成很多小的绘制指令
,然后再把这些指令按照顺序 组成一个待绘制列表
绘制操作是由渲染引擎中的合成线程 来完成的(由主线程 把绘制列表
提交给合成线程)
绘制列表 => 合成线程
合成线程的工作做过程:
前置原因分析:有些情况下,有的图层可以很大,有的页面你使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。(图块大小一般为:256×256 / 512×512)
视口:屏幕上页面的可见区域
- 合成线程会将图层划分为图块(tile)
- 按照视口附近的图块来优先生成位图(生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图)
- 图块是栅格化执行的最小单位
- 渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的
- 通常,栅格化过程都会使用 GPU 来加速生成
- 合成和显示:一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令------"DrawQuad",然后将该命令提交给浏览器进程。
浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
重排、重绘、合成
回流一定会触发重绘
- 重排:更新了元素的几何属性 ,如:
宽
、高
- 重绘:更新元素的绘制属性 ,如:
背景颜色
- 合成:更改一个既不要布局也不要绘制的属性,如:
transform
对比:
- 重排需要更新完整的渲染流水线,所以开销也是最大的。
- 相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
- 相对于重绘和重排,合成能大大提升绘制效率。
因此,如果要做性能优化,首先要考虑避免多次重排
如何避免触发回流和重绘
CSS:
- 避免使用
table
布局。 - 尽可能在DOM树的最末端改变
class
。 - 避免设置多层内联样式。
- 将动画效果应用到
position
属性为absolute
或fixed
的元素上 - 避免使用CSS表达式(例如:
calc()
) - CSS3硬件加速(GPU加速)
JavaScript:
- 避免频繁操作样式,最好一次性重写
style
属性,或者将样式列表定义为class
并一次性更改class
属性 - 避免频繁操作DOM,创建一个
documentFragment
,在它上面应用所有DOM操作,最后再把它添加到文档中 - 也可以先为元素设置
display: none
,操作结束后再把它显示出来。因为在display
属性为none
的元素上进行的DOM操作不会引发回流和重绘 - 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流
总结
本篇的内容其实有点类似于一本书的目录,
从目录看,它不要求你对每一章都非常深入,但是对于每一步主要是干了什么,你应该知道。
不过,就自己而言,你可以根据这个目录去逐步掌握学习每一章的内容,直到把这本书看完~
当面试官随手翻开一个目录,问你这章主要干了什么时,也希望你可以流畅对答。
交流
好了,本文到此结束,欢迎来撩,一起学习🙋♂️~
想要了解面试专题进阶篇的,请持续关注面试专栏的更新~
面试相关的文章及代码demo,后续打算在这个仓库(JS-banana/interview: 面试不完全指北 (github.com))进行维护,欢迎✨star,提建议,一起进步~