虚拟dom如何提升性能?
如何降低dom的更新次数?
先温习下dom的渲染机制(https://www.jianshu.com/p/fb7db9690e0d),当浏览器拿到了dom和css的骨架结构之后,下面就是将dom树的各元素根据尺寸和位置进行布局排列,这就是layout;然后对各元素进行上色,就是paint。
以上过程是必不可少的,其实我们关注的重点是:一旦浏览器解析dom完毕,如何降低dom的更新次数?
触发重新布局的话就是reflow,重新上色的话就是repaint。
比如有这么一个ul:
<ul>
<li>1</li>
<li>2</li>
</ul>
浏览器解析完毕后,如果对第2个li进行display:none和display:block的话,那就是触发了两次reflow和repaint。
脱离文档流
对脱离文档流的元素进行操作,就可以有效减少文档流本身的重排。
我们熟知的css脱离文档流的方式主要是float浮动与position定位,将频繁重排的元素,position属性设为absolute或fixed是个不错的选择,因为元素脱离了文档流,它的变化不会影响到其他元素。
还有一个比较常用的,就是文档碎片DocumentFragment。
比如我们要给ul添加5个li节点,二者的区别是:
直接操作DOM,需要重排5次:
image使用DocumentFragment一次性添加,只需重排1次:
image为检验结论是否正确,我们写段测试代码,分别对比下渲染完成的时间:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul id="list1"></ul>
<ul id="list"></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")
//使用documentFragment添加节点
console.time("time")
let list = document.querySelector("#list"),
fragment = document.createDocumentFragment(),
n = 50000;
while(n--){
fragment.appendChild(document.createElement("li"));
};
list.appendChild(fragment);
console.timeEnd("time")
</script>
运行结果:
time1:26 毫秒
time:24 毫秒
PS:
这段代码很有意思,如果你在谷歌浏览器测试,结果反而是直接操作dom比较快,这是因为谷歌本身做了优化设计,会将短时间内的多次reflow收集起来组成队列,在一定时间后flush队列,将多个reflow的变为一次reflow,可以多跑几款浏览器看看。
虚拟dom
以vue为例,虚拟dom之所以能提升性能,核心思想就是将真实dom映射为js的object,通过捕获收集object的差异,将对dom的操作一次性压入DocumentFragment,等待下一次事件循环再进行reflow,这样就有效减少了dom的操作次数,提升了渲染效率。