为了解决性能的问题。
执行JS有一个JS引擎,那么执行渲染也有一个渲染引擎。
渲染引擎在不同的浏览器中也不是都相同的。
在**Firefox中叫做Gecko,在Chrome和Safari中都是基于WebKit开发的**。
学习关于WebKit的这部分渲染引擎内容。
### 浏览器接受到HTML文件并转化为DOM树
当我们打开一个网页时,浏览器都会去请求对应的HTML文件。
在网络中传输的内容其实都是0和1这些字节数据。
当浏览器接收到这些字节数据以后,他会**将这些字节数据转为字符串**(我们的代码)
![](https://box.kancloud.cn/7bcfa616eab96e6e1b4e4bb98c359fab_311x83.png)
当数据转为字符串以后,浏览器会先**将这些字符串通过词法分析转为标记**(token),这一过程在词法分析中叫做标记化(tokenization)
![](https://box.kancloud.cn/fa6bb773e2db46597a3bfe6f203767d0_443x96.png)
标记(编译原理),标记还是字符串,是构成代码的最小单位。这一过程会将代码分拆成一块块,并给这些内容打上标记,便于理解这些最小单位的代码是什么意思。
![](https://box.kancloud.cn/978f4aff9fff5329eca7052d0200b1e1_460x205.png)
当结束标记化后,这些**标记紧接着会准换为Node**,最后这些Node会根据不同Node之前的联系**构建为一颗DOM树**。
![](https://box.kancloud.cn/703be5ff528c6a0d097aaffc99a344b9_521x266.png)
以上就是浏览器从网络中接收到HTML文件然后一系列的转换过程。
![](https://box.kancloud.cn/bdf39c65a9d4b16cbb09a3bd33e3c140_619x114.png)
在解析HTML文件的时候,浏览器还会遇到CSS和JS文件,这时候浏览器也会去下载并解析这些文件
### 将CSS文件转换为CSSOM树
转换CSS到CSSOM树的过程和上一节的过程类似
![](https://box.kancloud.cn/d6a9a5cea96daca6d1234dcd9c53be55_664x104.png)
在这一个过程中,浏览器会确定下**每一个节点的样式到底是什么**,并且这一过程其实是很**消耗资源**的。
因为样式你可以**自行设置**给摸个节点,也可以通过**继承**获得。在这一过程中,浏览器得**递归CSSOM树**,然后确定具体的元素到底是什么样式。
~~~
<div>
<a> <span></span> </a>
</div>
<style>
span {
color: red;
}
div > a > span {
color: red;
}
</style>
~~~
分析为什么消耗资源:
第一种设置样式:浏览器只需要找到页面中所以span标签,然后设置颜色
第二种设置样式:浏览器先找到所有的span标签,再找到span标签上的a标签,最后再去找到div标签,然后给符合这种条件的span设置颜色。**递归的过程很复杂**。
所以:
我们应该尽可能的**避免写过于具体的CSS选择器**,然后读**HTML来说也尽量少的添加无意义的标签**,保证**层级扁平**。
### 生成渲染树
当我们生成DOM树和CSSOM树以后,就需要将这两棵树**组合为渲染树**。
![](https://box.kancloud.cn/e4af542e3a75b66c664e5522789ae5c3_686x352.png)
不是简单的将两者合并就行了,渲染树只会包括**需要显示的节点**和这些节点的样式信息,**`display:none`**,就不会在渲染树张显示。
当浏览器生成渲染树以后,就会根据渲染树来**进行布局**(也可以叫做**回流**),然后**调用GUP绘制,合成图层**,显示在屏幕上。
### 为什么操作DOM慢
因为DOM是属于**渲染引擎**中东西,而JS又是**JS引擎**中的东西,当我们通过JS操作DOM的时候,其实这个操作涉及到了**两个线程之间的通信**,那么势必会带来一些**性能上的损耗**。操作DOM一多,也就等同于**一直在进行线程之间的通信**,并且操作DOM可能还会带来**重绘回流**的情况,所以也就导致了性能上的问题。
### 插入几万个DOM,如何不卡顿?
首先我们肯定**不能一次把几万个DOM全部插入,这样肯定会造成卡顿**。
解决问题的重点:如何**分批次部分渲染DOM**
大部分人:通过**requestAnimationFrame的方式去循环的插入DOM**,
其他解决方案:虚拟滚动(virtualized scroller)
**这种技术的原理就是只渲染可视区域内的内容,非可视区域的就完全不渲染了,当用户在滚动的时候,就去实时替换渲染的内容**。
### 什么情况阻塞渲染
首先渲染的前提是生成渲染树,所以**HTML和CSS肯定会阻塞渲染**。如果你想渲染的越快,你越应该降低一开始需要渲染的文件大小,并且**扁平层级,优化选择器**。
当浏览器在**解析到script标签时,会暂停构建DOM**,完成后才会从暂停的地方重新开始。如果你想首屏渲染的越快,就越不应该在首屏就加载JS文件,这也是都建议**将script标签放在body标签底部的原因**。
并不是说script标签必须放在底部,也可以给**script标签添加defer或者async属性**。
当script标签加上defer属性以后,表示该**JS文件会并行下载**。但是会**放到HTML解析完成后顺序执行**,所以对于这种情况可以**把script标签放在任意位置**。
对于没有任何依赖的js文件,可**以加上async属性,表示JS文件下载和解析不会阻塞渲染**。
## 重绘和回流
重绘和回流会在我们**设置节点样式时**频繁出现,同时也会很大程度上**影响性能**。
* 重绘时当节点需要**更改外观而不会影响布局**的,比如改变color就叫重绘。
* 回流是**布局或者几何属性需要改变**就称为回流。
* **回流必定发生重绘,重绘不一定引发回流**。
* 回流所需的成本比重绘高的多,改变父节点的子节点很可能会导致父节点一系列回流
以下几个动作可能会导致性能问题
* 改变window大小
* 改变字体
* 添加或删除样式
* 文字改变
* 定位或浮动
* 盒模型
重绘和回流其实也和EventLoop有关
1. 当Eventloop执行完Microtasks后,会判断document是否需要更新,因为浏**览器是60Hz的刷新率**,每16.6s才会更新一次
2. 然后判断是否有resize或者scroll事件,有的话回去触发事件,所以resize和scroll事件也是至少16ms才会触发一次,并且自带节流功能。
3. 判断是否触发了media query
4. 更新动画并且发送事件
5. 判断是否有全屏操作
6. 执行requestAnimationFrame回调
7. 执行IntersectionObserver回调,该方法用于判断元素是否可见,可以用于懒加载上,但兼容性不好
8. 更新界面
9. 以上就是每一帧中可能会做的事情,如果在一帧中有空闲时间,就回去执行**requestIdleCallback回调**。
requestAnimationFrame的回调会在每一帧确定执行,属于高优先级任务,而requestIdleCallback的回调则不一定,属于低优先级任务。
我们所看到的网页,都是浏览器一帧一帧绘制出来的,通常认为FPS为60的时候是比较流畅的,而FPS为个位数的时候就属于用户可以感知到的卡顿了
我们都知道React 16实现了新的调度策略(Fiber), 新的调度策略提到的异步、可中断,其实就是基于浏览器的 requestIdleCallback和requestAnimationFrame两个API。
requestIdleCallback回调的执行的前提条件是当前浏览器处于空闲状态。
### 减少重绘和回流
1.** 使用transform替代top**
~~~
<div class="test"></div>
<style>
.test {
position: absolute;
top: 10px;
width: 100px;
height: 100px;
background: red;
}
</style>
<script>
setTimeout(() => {
// 引起回流
document.querySelector('.test').style.top = '100px'
}, 1000)
</script>
~~~
2. **使用visibility替换display:none**,因为前者只会引起重绘,后者会引发回流(改变了布局)
3. 不要把节点的属性值放在一个循环里当成循环里的变量
~~~
for(let i=0; i<1000; i++){
//获取offsetTop会导致回流,因为需要去获取正确的值
console.log(document.querySelector('.test').style.offsetTop)
}
~~~
4. 不要使用table布局,可能很小的一个小改动会造成整个table的重新布局
5. 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用requestAnimationFrame
6. CSS选择符**从右往左匹配查找**,避免节点层级过多
7. 将**频繁重绘或者回流的节点设置为图层**,图层能够阻止该节点的渲染行为影响别的节点,比如video标签来说,浏览器会自动将该节点变为图层
8. 设置节点为图层的方式有很多,我们可以通过以下常用属性可以生成新图层
* will-change
* video、iframe标签
## 思考题
在不考虑缓存和优化网络协议的前提下,考虑可以通过哪些方式来最快新人页面,也就是常说的**关键渲染路径**,这部分也是性能优化的一块内容
怎么测量到底有没有加快渲染速度呢?
![](https://box.kancloud.cn/808374ff35b705c681c207c55182c1f0_913x397.png)
**当发生DOMContentLoaded事件后,就会生成渲染树**,生成渲染树就可以进行渲染了,这一过程更大程度上和硬件有关系了
![](https://box.kancloud.cn/1e3179a273e764672566f2fbf7f7a9c7_988x592.png)
当文档中没有脚本时,浏览器解析完文档便能触发DOMContentLoaded事件;如果文档中包含脚本,则脚本会阻塞文档的解析,而脚本需要等位于脚本前面的css加载完才能执行。
任何情况下,DOMContentLoaded的触发**不需要等待图片等其他资源加载完成**。页面上所有的**资源(图片,音频,视频等)**被加载以后才会触发load事件,简单来说,页面的load事件会在DOMContentLoaded被触发之后才触发。
**提示如何加速**:
1. 从文件大小考虑
2. 从script标签使用上考虑
3. 从css,htmldiam书写上来考虑
4. 从需要下载的内容是否需要在首屏使用上来考虑
- 空白目录
- 双樾
- JS基础知识
- JS-WEB-API
- 开发环境
- 运行环境
- ES6
- 原型
- 异步
- 虚拟dom
- mvvm
- 组件化和React
- hybrid
- 其他
- 补充
- 技巧
- 快乐动起来呀
- css
- 掘金小册子
- js基础知识
- ES6知识点
- JS异步
- JS进阶知识
- 思考题
- DevTools Tips
- 浏览器基础知识
- 浏览器缓存机制0
- 浏览器渲染原理
- 安全防范知识点0
- 从V8中看JS性能优化0
- 性能优化琐碎事
- Webpack性能优化0
- 实现小型打包工具0
- React和Vue
- Vue生命周期
- vue基础知识点
- Vue响应式
- vue高级
- React基础
- Vue.js技术解密
- 准备工作
- 数据驱动
- new Vue()
- vue实例挂载
- 组件化
- 深入响应式原理
- 编译
- 扩展
- Vue Router
- Vuex