ZiuChen.github.io/docs/article/深入理解浏览器运行原理.md
2023-02-05 15:07:58 +08:00

286 lines
10 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 深入理解浏览器运行原理
## 网页解析过程
输入域名 => DNS解析为IP => 目标服务器返回`index.html`
> DNSDomain Name System
## HTML解析过程
- 浏览器开始解析`index.html`文件,当遇到`<link>`则向服务器请求下载`.css`文件
- 遇到`<script>`标签则向服务器请求下载`.js`文件
<img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0a90afa7c2534ae78f3eab9200a0095b~tplv-k3u1fbpfcp-watermark.image?" alt="浏览器解析HTML过程" width="70%" />
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/630cf2fd1d3748d6846fe7f2ab99a01b~tplv-k3u1fbpfcp-watermark.image?" alt="浏览器是和如何工作的" width="70%" />
[How browsers work](https://web.dev/howbrowserswork/)
## 生成CSS规则
在解析的过程中,如果遇到`<link>`元素那么会由浏览器负责下载对应的CSS文件
- 注意下载CSS文件不会影响到DOM解析
- 有单独一个线程对CSS文件进行下载与解析
浏览器下载完CSS文件后就会对CSS文件进行解析解析出对应的规则树
- 我们可以称之为CSSOMCSS Object ModelCSS对象模型
## 构建Render Tree
有了DOM Tree和CSSOM Tree之后就可以将二者结合构建Render Tree了
此时如果有某些元素的CSS属性`display: none;`那么这个元素就不会出现在Render Tree中
- 下载和解析CSS文件时不会阻塞DOM Tree的构建过程
- 但会阻塞Render Tree的构建过程因为需要对应的CSSOM Tree
## 布局和绘制(Layout & Paint)
第四步是在渲染树Render Tree上运行布局Layout以计算每个节点的几何体
- 渲染树会表示显示哪些节点以及其他的样式,但是不表示每个节点的尺寸、位置等信息
- 布局是确定呈现树中所有节点的宽度、高度和位置信息
第五步是将每个节点绘制Paint到屏幕上
- 在绘制阶段浏览器布局阶段计算的每个frame转为屏幕上实际的像素点
- 包括将元素的可见部分进行绘制,比如文本、颜色、边框、阴影、替换元素
## 回流和重绘(Reflow & )
回流也可称为重排
理解回流Reflow
- 第一次确定节点的大小和位置称之为布局layout
- 之后对节点的大小、位置修改重新计算,称之为回流
什么情况下会引起回流?
- DOM 结构发生改变(添加新的节点或者移除节点)
- 改变了布局修改了width height padding font-size等值
- 窗口resize修改了窗口的尺寸等
- 调用getComputedStyle方法获取尺寸、位置信息
理解重绘Repaint
- 第一次渲染内容称之为绘制paint
- 之后的重新渲染称之为重绘
什么情况下会引起重绘?
- 修改背景色、文字颜色、边框颜色、样式等
**回流一定会引起重绘,所以回流是一件很消耗性能的事情**
- 开发中要尽量避免发生回流
- 修改样式尽量一次性修改完毕
- 例如通过cssText一次性设置样式或通过修改class的方式修改样式
- 尽量避免频繁的操作DOM
- 可以在一个DocumentFragment或者父元素中将要操作的DOM操作完成再一次性插入到DOM树中
- 尽量避免通过getComputedStyle获取元素尺寸、位置等信息
- 对某些元素使用position的absolute或fixed属性
- 并不是不会引起回流,而是开销相对较小,不会对其他元素产生影响
## 特殊解析: composite合成
在绘制的过程中,可以将布局后的元素绘制到多个合成图层中
- 这是浏览器的一种优化手段
- 将不同流生成的不同Layer进行合并
```
标准流 => LayouTree => RenderLayer
`position:fixed;` => RenderLayer
```
默认情况标准流中的内容都是被绘制在同一个图层Layer中的
而一些特殊的属性浏览器会创建一个新的合成层CompositingLayer并且新的图层可以利用GPU来加速绘制
- 每个合成层都是单独渲染的
- 单独渲染可以避免所有的动画都在同一层中渲染导致性能问题
- 在各自的层中渲染完成后,只需要将渲染结果更新回合成层即可
当元素具有哪些属性时,浏览器会为其创建新的合成层呢?
- 3D Transforms
- video canvas iframe
- opacity 动画转换时
- position: fixed
- will-change: 一个实验性的属性,提前告诉浏览器此元素可能发生哪些变化
- animation 或 transition设置了opacity、transform
### 案例1同一层渲染
```
.box1 {
 width: 100px;
 height: 100px;
 background-color: red;
}
.box2 {
 width: 100px;
 height: 100px;
 background-color: blue;
}
```
```
<body>
 <div class="box1"></div>
 <div class="box2"></div>
</body>
```
在开发者工具的图层工具中可以看到,两个元素`.box1``.box2`都是在一个层Document下渲染的
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/373fb3a4d5284e6c80c3ec519918e6e4~tplv-k3u1fbpfcp-watermark.image?" alt="image-20221122103111654.png" width="70%" />
### 案例2分层渲染
当我们为`.box2`添加上`position: fixed;`属性,这时`.box2`将在由浏览器创建出来的合成层,分层单独渲染
```
.box2 {
 width: 100px;
 height: 100px;
 background-color: blue;
 position: fixed;
}
```
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6116d83ee8c041f586d627e549fdd5bf~tplv-k3u1fbpfcp-watermark.image?" alt="image-20221122103256116.png" width="70%" />
### 案例3transform 3D
为元素添加上`transform`属性时浏览器也会为对应元素创建一个合成层需要注意的是只有3D的变化浏览器才会创建
如果是`translateX``translateY`则不会
```
.box2 {
 width: 100px;
 height: 100px;
 background-color: blue;
 /* position: fixed; */
 transform: translateZ(10px);
}
```
<img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/befb4826b079439f81c98b03586a36e5~tplv-k3u1fbpfcp-watermark.image?" alt="image-20221122103715428.png" width="70%" />
### 案例4transition+transform
当我们为元素添加上动画时,动画的中间执行过程的渲染会在新的图层上进行,但是中间动画渲染完成后,结果会回到原始图层上
```
.box2 {
 width: 100px;
 height: 100px;
 background-color: blue;
 transition: transform 0.5s ease;
}
.box2:hover {
 transform: translateY(10px);
}
```
- 这也是使用`transform`执行动画性能更高的原因,因为浏览器会为动画的执行过程单独创建一个合成层
- 如果是通过修改`top` `left`等定位属性实现的动画,是在原始的图层上渲染完成的。“牵一发则动全身”,动画过程中将导致整个渲染树回流与重绘,极大的影响性能
### 案例5transition+opacity
`transform`类似,使用`transition`过渡的`opacity`动画,浏览器也会为其创建一个合成层
```
.box2 {
 width: 100px;
 height: 100px;
 background-color: blue;
 opacity: 1;
 transition: opacity 0.5s ease;
}
.box2:hover {
 opacity: 0.2;
}
```
### 总结
分层确实可以提高性能但是它是以内存管理为代价的因此不应当作为Web性能优化策略的一部分过度使用
## 浏览器对script元素的处理
之前我们说到,在解析到`link`标签时浏览器会异步下载其中的css文件并在DOM树构建完成后将其与CSS Tree合成为RenderTree
但是当浏览器解析到`script`标签时,整个解析过程将被阻塞,当前`script`标签后面的DOM树将停止解析直到当前`script`代码被下载、解析、执行完毕才会继续解析HTML构建DOM树
为什么要这样做呢?
- 这是因为Javascript的作用之一就是操作DOM并且可以修改DOM
- 如果我们等到DOM树构建完成并且渲染出来了再去执行Javascript会造成回流和重绘严重影响页面性能
- 所以当浏览器构建DOM树遇到`script`标签时会优先下载和执行Javascript代码而后再继续构建DOM树
这也会带来新的问题,比如在现代的页面开发中:
- 脚本往往比HTML更“重”浏览器也需要花更多的时间去处理脚本
- 会造成页面的解析阻塞,在脚本下载、解析、执行完成之前,用户在界面上什么也看不到
为了解决这个问题,浏览器的`script`标签为我们提供了两个属性attribute`defer``async`
## defer属性
`defer` 即推迟,为`script`标签添加这个属性相当于告诉浏览器不要等待此脚本下载而是继续解析HTML构建DOM Tree
- 脚本将由浏览器进行下载但是不会阻塞DOM Tree的构建过程
- 如果脚本提前下载好了那么它会等待DOM Tree构建完成`DOMContentLoaded`**事件触发之前**先执行`defer`中的代码
```
<script>
 console.log('script enter')
 window.addEventListener('DOMContentLoaded', () => {
   console.log('DOMContentLoaded enter')
})
</script>
<script src="./defer.js" defer></script>
```
```
// defer.js
console.log('defer script enter')
```
上述代码在控制台的输出为:
```
script enter
defer script enter
DOMContentLoaded enter
```
- 多个带`defer`的脚本也是按照自上至下的顺序执行的
- 从某种角度来说,`defer`可以提高页面的性能,并且推荐放到`head`元素中
- 注意:`defer`仅适用于外部脚本,对于`script`标签内编写的默认`JS`代码会被忽略掉
## async属性
`async`属性也可以做到让脚本异步加载而不阻塞DOM树的构建它与`defer`的区别:
-`async`标记的脚本是**完全独立**的
- `async`脚本不能保证执行顺序,因为它是独立下载、独立运行,不会等待其他脚本
- 使用`async`标记的脚本不会保证它将在`DOMContentLoaded`之前或之后被执行
要使用`async`属性标记的`script`操作DOM必须在其中使用`DOMContentLoaded`监听器的回调函数在该事件触发DOM树构建完毕执行相应的回调函数