虚拟 DOM 和 DOM diff

204 阅读4分钟

理解虚拟DOM是什么

虚拟DOM可以把它想象成一颗以JS对象作为基础的树,这棵树模拟了真实的DOM,再通过虚拟DOM树节点之间进行比较,从而实现DOM的操作与更新。再通过方法的转换,将它变成真实DOM。

在JS中,虚拟 DOM 表现为一个 Object 对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性。不同的框架对这三个属性的名命可能会有差别。

为什么需要虚拟DOM

  • 如果我们在1000个节点中修改其中10个。原生JS操作DOM时,浏览器并不知道我们只有10个节点需要修改,因此它会将整个1000个节点全部替换一遍,这样做其实里面的990个节点都是在做无用功,白白浪费很多性能。而虚拟DOM就是为了节省不必要的流程而被设计出来。在1000个节点中如果只修改其中10个的话,虚拟DOM并不会直接去操作DOM,而是将这10个需要更新的节点内容保存在本地的JS对象中,直到需要执行时再将这个JS对象一次性插入到DOM树中,再对节点进行对比,找出真正需要更新的节点来进行DOM操作。这样做避免了重新操作无需改动的节点,从而使得性能有所提升。
  • 虚拟DOM还可以跨平台使用。因为虚拟 DOM 不仅可以变成 DOM,还可以变成小程序、iOS 应用、安卓应用,因为虚拟 DOM 本质上只是一个 JS 对象

使用DOM需要注意的问题

  • 当DOM数据规模过于庞大的时候(比如新增10万个节点),虚拟DOM的性能不减反增,要比真正的DOM慢得多(Vue操作虚拟DOM会自动优化,没有这个问题)
  • 需要额外的创建函数,如create Element或h,但是可以通过JSX来简化成XML写法

DOM代码是什么样的

React

createElement('div',{className:'red',onClick:()=> {}},[
    createElement('span', {}, 'span1'),
    createElement('span', {}, 'span2')
  ]
)

Vue

h('div', {
  class: 'red',
  on: {
    click: () => { }
  },
}, [h('span',{},'span1'), h('span', {}, 'span2'])

理解什么是DOM diff

两个虚拟DOM树对比区别的算法即为DOM diff。diff 算法仅在两个树的同级的虚拟节点之间做比较,递归地进行比较,最终实现整个 DOM 树的更新

DOM diff的大概逻辑

  • 首先DOM diff得到两棵树
  • 将新旧两棵树逐层对比,找出哪些节点需要更新
  • 如果节点是组件就看 Component diff(组件逻辑)
  • 如果节点是标签(包括文本)就看 Element diff(元素逻辑)

Component diff(组件逻辑)

  • 如果节点是组件,就先看组件类型
  • 类型不同直接替换(删除旧的)
  • 类型相同则只更新属性
  • 然后深入组件做 Tree diff(递归)

Element diff(元素逻辑)

  • 如果节点是原生标签,则看标签名
  • 标签名不同直接替换,相同则只更新属性
  • 然后进入标签后代做 Tree diff(递归)

DOM diff的问题

同级节点对比存在bug,会出现识别错误的问题

key值的问题

浏览器在对元素做比较的时候,首先会同级别进行比较。如下图,没有使用key的时候,你想把第二个方块删除掉,而结果却是留下了三角和方块,圆形却消失了

这是因为,计算机做了两件事:

  • 把2变成了3
  • 然后把3删除了 这是为什么呢?因为计算机会这样做
  • 遍历数组:对比1和1,发现1没变。复用之前的所有内容
  • 对比2和3,发现2变成了3。既然2变成了3,那么正方形左边的2就变更成3。里面的正方形就地复用
  • 对比undefined和3发现3被删除了。之前的圆形删掉,里面的子元素也删除 所以结论是:留下了三角形和方形,圆形消失

解决方法

给每个元素一个id作为key值(不能用index作为key )

这样计算机就会这样做:

  • 首先发现id从123变成了13,说明第二项被删除了
  • 然后依次对此id:1的项和id:3的项,发现没变化。 所以计算机得出结论:第二项被删除了。bug解决