什么是Virtual DOM?为什么要使用它?
在vue,react这些框架中,都是通过监听数据的变化而相应的去渲染我们的视图.但是如果每次修改数据就去操作一次DOM树,这在性能上无疑是大打折扣的.虚拟DOM的出现就是为了解决这个问题,并且起到协调的作用.本质上就是在js和真实DOM树之间做了一个缓存,具体原理是:每次当数据发生变化时,渲染机制先在虚拟DOM上去改变视图,然后再跟实际DOM树作比较(作比较用到的是虚拟DOM的diff算法),再在实际DOM上更新差异部分(也就是这个时候才去操作实际DOM树,渲染差异部分),这样就减少了对实际DOM树的操作,达到性能优化的效果.
简单理解为:虚拟DOM树其实是实际DOM树更改之前的参考对象,是js实际操作DOM节点时与真实DOM树之间的一个中介(缓存).以达到减少对DOM节点的频繁无效的操作和性能优化的目的.
创建虚拟DOM
两种方式:
1.纯JS (一般不用这种)
2.JSX
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!-- 由于浏览器的js引擎不能直接解析JSX.需要babel.js转译为纯js代码 -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<title>react</title>
</head>
<body>
// 创建虚拟DOM容器
<div id="myVirt"></div>
<div id="myVirt2"></div>
<script type="text/babel"> // 使用jsx创建虚拟DOM时,type不能少
// 纯js创建虚拟DOM 创建标签 属性 文本内容
var str = React.createElement('div',{id:"myVirt",className:'myVirt'},'JS创建虚拟DOM');
console.log(str,1234)
// 渲染虚拟DOM:ReactDOM.render(virtualDOM,containerDOM)
ReactDOM.render(str,document.getElementById("myVirt"))
var text = "我是JSX虚拟DOM";
var viturlDiv = <div className="myVirt2">{text}</div>
// JSX创建虚拟DOM
ReactDOM.render(viturlDiv,document.getElementById("myVirt2"))
</script>
</body>
</html>
如何实现虚拟DOM与实际DOM之间的比较呢?
Diff算法:该算法并不是react独有的算法,react只是在传统diff算法的基础上做了一些优化而已.实现机制是:深度遍历DOM树,从根节点开始优先查找节点的左子树及其左节点,完全遍历完左子树之后才会开始去查找右子树,查找右子树时也是先从右子树的左节点开始往下查找,直到遍历完全.比较出两棵树之间的差异,然后在真实DOM树更新差异部分.
diff算法的比较规则:
- 节点不存在,直接创建该节点;
- 节点存在且节点类型相同,继而判断属性是否相同,不同则修改节点属性,无需删除该节点
- 节点存在且节点类型相同,继而判断文本内容是否相同,有差异则做出修改.
- 节点类型不同,直接删除元素及其子节点,替换成新节点
两个节点之间的差异包括:
1.直接替换原有节点
2.调整子节点,包括移动删除等操作
3.修改节点属性,文本内容
react diff:tree diff component diff element diff
官方建议不要出现跨层级的节点操作,当出现节点跨层级移动时,react并不会去移动节点,而是删除要移动的节点及其子节点并重新创建到移动位置,
场景假设:
假如我有一千个节点,当我删除第一个节点时,那我要去更新剩下的999个节点的位置,针对相同的节点只是位置发生变化就会出现大量的删除/创建节点的操作,这无疑会影响到react的性能.
*针对这一问题,react diff做出的优化措施是:*对同一层级的同组子节点(也就是父节点下的所有子节点)添加唯一的key来进行区分.
其实针对相同节点的操作,如果我有唯一的key值,我就根据这个key值去定位需要操作的节点,执行删除操作即可,也不会影响到其他节点.如果要移动节点之间的位置,同理根据key值找到对应的节点,交换位置并更新为新的节点集合即可,其他节点不需要发生任何操作.这样来达到优化.
key的作用主要是:
1.准确判断除当前节点是否在旧集合中
2.极大的减少遍历次数
当然也不是所有都加上key就是性能优化了,对于简单列表页渲染来说,不加key要比加了key的性能好.
例如:
<div>1</div> <div key="1">1<div>
<div>2</div> <div key="2">2<div>
<div>3</div> <div key="3">3<div>
<div>4</div> <div key="4">4<div>
<div>5</div> <div key="5">5<div>
我如果要修改节点2跟4的位置,没有key值的话我直接修改InnerText即可.加了key值之后我需要去找到key=2的节点然后再找到key=4的节点,然后再将两个节点调换位置.这样移动DOM节点的操作性能上就不如直接修改InnerText.
所以,请注意不是加了key就一定优化性能.
参考大佬博客:https://segmentfault.com/a/1190000018914249