从一道高频面试题来探究浏览器的工作原理
TIP
问题:从在浏览器地址栏输入直到在屏幕上看到页面的整个过程中都发生了些什么?
从地址栏输入到页面展示,大体上可分成两大流程:导航流程 和 渲染流程
一、导航流程
1.1、用户输入(浏览器进程)
当用户在地址栏中输入一个查询关键字,地址栏会判断这个关键字是 请求URL 还是 查询内容
- 查询内容:默认搜索引擎 + 关键字组成新的
URL
- 请求URL:请求
URL
+ 协议 组成完整的URL
,eg: baidu.com => https://www.baidu.com/
当前浏览器状态
Tab
标签页左侧icon
图标变成loading
状态- 页面还是显示当前页面内容
beforeunload
事件
- 当按下
enter
后,当前页面将会被替换成新的页面,在这之前,当前页面可执行beforeunload
事件 beforeunload
事件中执行数据清理、询问用户是否离开当前页(表单页面未提交)等,可用户可以通过beforeunload
事件来取消导航
1.2、URL
请求流程(网络进程)
当完整URL请求准备好后,浏览器进程会通过 IPC
将 URL
请求发给网络进程,网络进程收到URL
请求后,才发起真正的 URL
请求
1.2.1、判断是否“命中” 强缓存?
- 若命中,返回缓存资源给浏览器进程,
HTTP
状态码200
; - 反之,进入网络请求流程
1.2.2、网络请求流程
- DNS 域名解析
获取请求域名的服务器IP
地址。端口号未明确指定的,使用默认端口,HTTP
默认 80,HTTPS
默认 443,
如果请求协议是 HTTPS
,那么还需要建立 TLS
连接
- TCP 连接数量判断
Chrome
存在一机制,同一域名最多同时存在 6 个TCP
连接,后续的需要排队等候,未超过 6 个直接建立连接
构建请求行、请求头,发送请求信息
服务端返回响应数据
1.2.3、处理响应数据
是否重定向?
当返回的状态码是 301
或 302
,表示重定向,网络进程会从响应头的 Location
字段里面读取重定向的地址, 重新发起 HTTP/HTTPS
请求。
301
:永久重定向;302
暂时重定向
非重定向,=> next
判断相应数据类型
Content-Type
: 浏览器根据响应头中该字段判断服务器返回的响应体数据类型
application/octet-stream
:字节流类型。浏览器会将数据传递给下载管理器做进一步的文件下载或预览等工作text/html : html
格式: => next
1.3、准备渲染进程(浏览器进程)
对于新页面,采用的渲染进程如下:
- 1、默认情况,打开新的页面都会使用单独的渲染进程
- 2、同一站点情况,在 A 页面打开 B 页面,A、B页面属于同一站点,那么 B 就复用 A 的渲染进程
❓思考:浏览器进程何时开始准备一个渲染进程?
- 当浏览器进程将 请求
URL
传递给网络进程时,就知道了要访问的站点,此时浏览器进程就可以开始查找或启动一个渲染进程,这个动作与让网络线程下载数据是同时的。 - 当然,如果出现重定向的请求时,提前初始化的渲染进程可能就不会被使用了
1.4、提交文档(浏览器进程)
含义:浏览器进程 将 网络进程接收到的 HTML
数据提交给渲染进程
步骤:
1、
浏览器进程
接收到网络进程
的响应头数据后,立即向渲染进程
发起 “提交文档” 的消息2、
渲染进程
接收到提交文档消息后,与网络进程
之间建立传输数据的 管道3、等文档数据传输完成后,
渲染进程
发送 确定提交 的消息给浏览器进程
4、
浏览器进程
收到 确定提交 的消息后,会更新界面状态,包括安全状态、地址栏URL、前进后退的历史状态, 并更新Web 页面。
(PS: 👇 此时Tab标签左侧的 icon图标还是Loading
状态)
二、渲染流程(渲染进程)
渲染进程负责所有发生在浏览器页签中的事情。在一个渲染进程中,
- 主线程负责解析,编译或运行代码等工作,当我们使用
Worker
时,Worker
线程会负责运行一部分代码 - 合成线程和光栅线程是也是运行在渲染进程中的,负责更高效和顺畅的渲染页面
2.1、构建DOM树
HTML
文件经过 HTML
解析器解析,输出树状结构的 DOM
2.2、样式计算
计算中 DOM
节点中每个元素的具体样式 。分成以下三步
2.2.1、构建 styleSheets
渲染引擎将 CSS
文本转换成浏览器可以理解的结构:styleSheets
2.2.2、属性值的标准化
将类似 red、bold、2em
等值转换成渲染引擎容易理解的、标准化的计算值 rgb(255, 0, 0)、700、32px
等
2.2.3、计算每个节点的具体样式
- 继承
CSS
继承是指每个DOM 节点都包含着父节点的样式。然而并不是所有的样式属性都可以继承
- 层叠
CSS
层叠处于 CSS
核心地位,定义如何合并来自多个源的属性值的算法。最终计算输出的属性,可在控制台的 ComputedStyle
中查看到
2.3、布局
计算出 DOM
树中可见元素的几何位置。分成以下俩步
2.3.1、创建布局树
将 DOM
树中所有可见节点,添加到布局树中,而不可见的节点会被过滤掉,像 head
标签下内容,属性值 display: none
的节点等
同时,当我们使用一个包含内容的伪元素(例如 p::before {content: 'Hi!'}
)时,元素会出现在布局树中而不存在于 DOM
树中,这也是为什么使用 DOM 提供的 API 无法获取伪元素的原因
2.3.2、布局计算
2.4、分层
渲染引擎需要对特定的节点生成专门的图层,并生成一颗对于的图层树。浏览器的页面的页面实际上被分成多个图层,这些 图层叠加后合成了最终的页面 ,但不是每个节点都会生成一个图层,如果一个节点没有图层,那它就属于父节点的图层
对开发者来说,当某一部分需要用独立的层渲染,我们可以使用 css
属性will-change
让浏览器创建层
❓ :节点需要满足什么条件?渲染引擎才会为节点创建新的图层?
- 拥有层叠上下文属性的元素会被提升为单独一层,关于层叠上下文
- 需要裁剪(clip)的地方会被创建为图层
<video>
,<iframe>
等元素也会创建单独图层
2.5、图层绘制
2.5.1、生成待绘制列表(主线程)
主线程会把每一个图层的绘制拆分成很小的绘制指令,然后把这些指令按照顺序组成一个待绘制列表
2.5.2、栅格化(合成线程)
当图层的绘制列表准备好后,主线程
会把绘制列表提交给合成线程
。因为图层很大,而每次所看到的视口很小,没必要一次性绘制图层所有内容,
所以合成线程
会将图层划分成 图块(tile
),大小通常是 256*256
或 512*512
。合成线程会按照视口附件的图块来优先生成位图, 实际生成位图的操作是由栅格化来执行的。
所谓栅格化,是指将图块转化成位图
图块是栅格化最小执行单位,渲染进程中存在一个栅格化线程池,负责所有图块的栅格化
GPU 加速
栅格化过程通常使用 GPU
来加速生成,所有,此过程也叫做 GPU
栅格化,生成的位图都保留在 GPU
内存中
2.5.3、合成显示
当所有图块被栅格化成位图,合成线程
会生成一个绘制图块的命令 DrawQuad
提交给浏览器进程
浏览器进程
中的 viz
组件接收到该命令后,将页面内容绘制到内存中,最终将内存显示在屏幕上
2.6、重排、重绘与合成
- 重排
修改元素的宽、高、位置等。重排需要更新完整的渲染流水线,开销最大
- 重绘
修改元素的背景色、字体颜色等。重绘省去了布局和分层阶段,效率比重排高
- 合成
修改一个既不要布局也不要绘制的属性,渲染引擎将跳过布局和绘制,只执行最后面的合成操作。
使用 CSS 的 transform
实现动画,能避开重排和重绘,是直接在非主线程上执行动画,不占用主线程的资源
可使用 工具查看 CSS 某个属性的修改是否会导致重排、重绘以及合成等