虚拟DOM是什么

虚拟DOM就是用JS对象的形式,来表现真实的DOM树。

React的虚拟DOM

  1. const vNode = {
  2. key : null,
  3. props : {
  4. children : [ //子元素们
  5. {type : 'span',...},
  6. {type : 'span',...}
  7. ],
  8. className : "red" //标签上的属性
  9. onClick : ()=>{} //事件
  10. },
  11. ref : null,
  12. type : "div", //标签名or组件名
  13. ...
  14. }

Vue的虚拟DOM

  1. const vNode = {
  2. tag : "div", //标签名or组建名
  3. data : {
  4. class : "red", //标签上的属性
  5. on : {
  6. click : ()=>{} //事件
  7. }
  8. },
  9. children : [ //子元素们
  10. {tag : "span",...},
  11. {tag : "span",...}
  12. ],
  13. ...
  14. }

创建虚拟DOM

React.createElement

  1. createElement('div',{className:"red",onClick:()=>{}},[
  2. createElement('span',{},'span1'),
  3. createElement('span',{},'span2')
  4. ])

Vue(只能在render函数里得到h)

  1. h('div',{
  2. class:'red',
  3. on:{
  4. click:()=>{}
  5. },
  6. },[h('span',{},'span1'),h('span',{},'span2')])

改进版创建虚拟DOM方法

React

  1. <div className="red" onClick={fn}>
  2. <span>span1</span>
  3. <span>span2</span>
  4. </div>
  5. //需要通过babel转为createElement形式

Vue Template

  1. <div class="red" @click="fn">
  2. <span>span1</span>
  3. <span>span2</span>
  4. </div>
  5. //通过vue-loader转为h形式

虚拟DOM的优点

减少DOM操作
虚拟DOM可以讲多次操作合并为一次操作,比如你添加1000个节点,却是一个接一个操作的(减少频率)
虚拟DOM借助DOM diff可以把多余的操作省掉,比如你添加1000个节点,其实只是10个是新增的(减少范围)
跨平台
虚拟DOM不仅可以变成DOM,还可以变成小程序、IOS应用、安卓应用,因为虚拟DOM本质只是一个JS对象

虚拟DOM的缺点

需要额外的创建函数,如createElement或h,但可以用改进版的创建方法

DOM diff是什么

Dom diff是虚拟DOM的对比算法,该算法主要是对比两个虚拟DOM的不同点

举例解释

  1. <div :class="x">
  2. <span v-if="y">{string1}</span>
  3. <span>{string2}</span>
  4. </div>

图示,把虚拟DOM想象成树形
image.png

数据变化,x从red变成green

image.pngimage.png
DOM diff发现
div标签类型没变,只需要更新div对应的DOM属性
子元素没变,不更新

数据变化,y从true变成false

image.pngimage.png
DOM diff发现
div没变,不用更新
子元素1标签没变,但是children变了,更新DOM内容
子元素2不见了,删除对应的DOM

总结:
DOM diff其实是一个函数,我们称之为patch。函数接收两个虚拟DOM对象,然后通过函数内部的逻辑,找出两个虚拟DOM的区别: patches = patch(oldVNode, newVNode) ,而这个patches就是需要更新的DOM操作
DOM diff的逻辑
Tree diff:讲新旧两个DOM逐层对比,找出哪些节点需要更新。如果节点是组件就看Component diff
Component diff:如果节点是组件,就先看组价类型。类型不同直接替换(删除旧的)。类型相同则更新属性。然后深入组件做Tree diff(递归)
Element diff:如果节点是原生标签,则看标签名。标签名不同直接替换,相同则只更新属性。然后进入标签后代做Tree diff(递归)

DOM diff的优点

可以做到只把变化的部分重新渲染,提高渲染效率的过程

DOM diff的问题(key)

DOM diff也会存在有一个问题:同级对比存在bug
解释
回顾上面的 数据变化,y从true变成false 的DOM算法。我们的原意是把第一个span删除,留下第二个span。但是DOM diff算法的操作是发现了第一个span的内容又”hello”变成了”world”,所以把第一个span的内容变成了第二个span的内容,删除第二个。

所以,我们一般会给组件添加上key属性,这个key属性是节点的唯一标识。有了这个标识,在看上面的例子,DOM diff会发现第一个span的key不见了,就删除了第一个span,留下第二个span,完全符合我们的预期

总结:
key值的绑定对象不要使用index
因为当使用index作为key的值,那么在删除第二项的时候,index会从1 2 3变成 1 2(而不是1 3)。因为DOM diff会理解为,2发生了改变,在2的基础上做更新,把3的内容更新到2上,把3删除