虚拟 dom 是如何提升 vue 的渲染效率的(虚拟 dom 如何提升性能)?
虚拟DOM
1,dom 渲染原理
2,虚拟 dom 是什么?
3,虚拟 dom 在 vue 中做了什么?
4,虚拟 dom 是如何提升 vue 的渲染效率的(虚拟 dom 如何提升性能)?
1,dom渲染原理
以webkit为例,dom渲染的过程是怎样的呢?先来看张图:
大概分为以下几个步骤:
1,浏览器先下载html文件,html parser(解析器)会将其解析为 dom 树
2,下载css文件,css parser会将其解析为 cssom 树
3,dom和cssom合成为render tree(呈现树)
4,布局/回流(layout/reflow)
5,绘制(paint)
dom 树:
Cssom (css object model)树:
render树(呈现树/渲染树):
dom树是骨架,骨架构建完后就要读取css文件,每个dom元素会去cssom中查询自己的修饰信息,这就相当于合并dom和cssom的动作。而这个动作完成后,输出来的就是呈现树了。呈现树的长相跟dom树不太一样,突出的是呈现二字,也就是一定要是肉眼看得到的东西。比如标签就不会出现在呈现树中,因为不可见,同样的还有display:none的元素,呈现树也不会构建。
呈现树主要是根据每个dom元素的display信息,构建成一个个的方块。比如display:block的元素,会构建成一个大方块,而display:inline的元素,就构建成一个小方块。
换句话说,呈现树是由多个带有视觉属性(比如颜色和尺寸)的方块组成。
呈现树的节点和dom树并不是一一对应的。比如select元素,dom树只有一个,而呈现树则会构建出三个:一个是显示区域,一个是下拉列表框,还有一个用于按钮
布局、回流:
构建完呈现树后,我们需要根据每个元素的位置将其都摆放好,也就是为每个节点分配一个应出现在屏幕上的确切坐标,这就是layout布局。
这里多啰嗦几句,玩过PS的同学都知道psd图,在设计psd时,都是先一个一个小图单独分层绘制,然后根据客户的要求统一排版,比如这个图往左边挪一下,那个图靠右边点这样。呈现树也类似,先把草稿绘制好,最后根据css规定的位置(客户要求)统一进行排版,这就是布局。
当呈现树发生了变化时(比如js操作dom让某元素的位置发生了改变),那就得重新布局,这就是reflow(回流)。
绘制:
布局动作完成后,GUI渲染线程会遍历呈现树,将每个节点绘制出来,这就是paint绘制。可以想象成喷墨打印机开始打印了
2,虚拟 dom 是什么?
-
vue2.x才有虚拟dom的 -
虚拟 dom 本质就是一个js对象,所以具有跨平台的特质
3,虚拟 dom 在 vue 中做了什么?
首先先看下vue的渲染过程:
-
第一步:写一个div元素 -
第二步:vue中有一个render()函数,render()负责将 template 模板里的东西转化为真实的 dom,比如会根据 template 中的 div 元素,通过 createElement 创建一个真实的 div,这时候就会生成对应的虚拟 dom -
第三步:会根据创建好的真实 dom 生成虚拟 dom 也就是 js 对象,最后再变成真实 dom, 然后浏览器最终呈现
所以虚拟 dom 在 vue 中做了:
-
1,将真实 dom 转为 虚拟 dom (js 对象) -
2,更新的时候做对比
4,虚拟 dom 是如何提升 vue 的渲染效率的(虚拟 dom 如何提升性能)?
上文dom渲染原理中也有提到,当浏览器拿到 dom 和 css 的骨架结构后,接着就是将 dom 树的各元素根据尺寸和位置进行布局排列,这就是layout。然后对各元素进行上色,这就是paint。
以上过程是必不可少的,其实我们关注的重点是:一旦浏览器解析 dom 完毕,如何降低 dom 的更新次数?
触发重新布局的话就是reflow(回流),重新上色的话就是paint(绘制)。
<ul>
<li>1</li>
<li>2</li>
</ul>
浏览器解析完毕后,如果对第二个 li 进行 display: none、display: block的话,那就是触发了两次reflow和repaint(重新画)
脱离文档流
对脱离文档流的元素进行操作,就可以有效减少文档流本身的重排。我们熟知的css脱离文档流的方式主要是float浮动与position定位,将频繁重排的元素,position属性设为absolute或fixed是个不错的选择,因为元素脱离了文档流,它的变化不会影响到其他元素。
还有一个比较常用的,就是文档碎片DocumentFragment
比如我们要给ul添加5个li节点,二者的区别是:
直接操作DOM,需要重排5次:
使用DocumentFragment一次性添加,只需重排1次:
为检验结论是否正确,我们写段测试代码,分别对比下渲染完成的时间:
<body>
<ul class="list1"></ul>
<ul class="list2"></ul>
</body>
</html>
<script>
// 直接操作 DOM 添加节点
console.time('time1')
let list1 = document.querySelector('.list1'),
i = 50000
while (i--) {
list1.appendChild(document.createElement('li'))
}
console.timeEnd('time1')
// 使用 documentFragement 添加节点
console.time('time2')
let list2 = document.querySelector('.list2'),
fragment = document.createDocumentFragment(),
n = 50000
while(n--) {
fragment.appendChild(document.createElement('li'))
}
list2.appendChild(fragment)
console.timeEnd('time2')
</script>
运行结果:
time1:26 毫秒
time:24 毫秒
PS:
这段代码很有意思,如果在谷歌浏览器测试,结果反而是直接操作dom比较快,这是因为谷歌本身做了优化设计,会将短时间内的多次reflow收集起来组成队列,在一定时间后flush队列,将多个reflow的变为一次reflow,可以多跑几款浏览器看看
虚拟dom:
以vue为例,虚拟 dom 之所以能提升性能,核心思想就是将真实 dom 映射为 js 对象,通过捕获收集object的差异,将对dom的操作一次性压入DocumentFragment,等待下一次事件循环再进行reflow,这样就有效减少了dom的操作次数,提升了渲染效率。
vue的两大核心:组件化、数据驱动
-
1,局部更新(节点数据) -
2,将直接操作 dom 的地方拿到两个 js 对象之中去进行比较