虚拟DOM和DOM diff

122 阅读3分钟

虚拟DOM

虚拟DOM是什么?
一个能代表DOM树的对象,通常含有标签名、标签上的属性、事件监听和子元素们,以及其他属性。

虚拟DOM长什么样子

  • React
  const vNode = {
    key: null,
    props: {
      children: [ //子元素们
         {type: 'span',...},
         {type: 'span',...}
      ],
      className: "red" //标签上的属性
      onClick:()=>{} //事件
    }
    ref:null,
    type:"div",// 标签名或组件名
    ...
  }
  • Vue
 const vNode = {
   tag: "div",// 标签名或组件名
   data: {
     class: "red",//标签上的属性
     on: {
      click: () => {}//事件
     }
   },
   children: [ //子元素们
     {type: 'span',...},
     {type: 'span',...}
   ]
 }

简化创建虚拟DOM

  • React jsx
 <div className="red" onClick="{()=>{}}">
 <span>span1</span>
 <span>span2</span>
 </div>
  • Vue Template
 h('div',{
   class: 'red',
   on: {
     click:()=>{}
   }
 },[h('span',{},'span1'),h('span',{},'span2')])

最终创建虚拟DOM的方法

  • React
    通过babel转为createElement形式
<div className="red" onClick={fn}>
    <span>span1</span>
    <span>span2</span>
</div>
  • Vue Template
    通过vue-loader转为h形式
<div class="red" @click="fn">
    <span>span1</span>
    <span>span2</span>
</div>

虚拟DOM的优点

减少DOM操作次数
虚拟DOM可以将多次操作合并为一次操作(减少操作次数),比如要添加1000个节点,如果不用虚拟DOM操作,是要一个接一个操作的。
虚拟DOM借助DOM diff可以把多余的操作省掉,比如要添加1000个节点,其实只有10个是新增的。(减少操作次数)

跨平台
虚拟DOM不仅可以变成DOM,还可以变成小程序、iOS应用、安卓应用,因为虚拟DOM本质上只是一个JS对象。

虚拟DOM的缺点

需要额外的创建函数,如createElement或h,但可以通过JSX来简化成XML写法。

DOM diff

DOM diff就是虚拟DOM的对比算法。
我们把虚拟DOM想像成树形,代码如下:

<div :class="x">
   <span v-if="y">{string1}</span>
   <span>{string2}</span>
</div>

代码对应的图示如下:

image.png

当数据变化时

  • x从red变成green时

image.png DOM diff发现:

  1. div标签类型没变,只需要更新div对应的DOM的属性
  2. 子元素没变,不更新
  • y从true变成false时

image.png
DOM diff发现:

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

DOM diff的逻辑

Tree diff
1.将新旧两颗树逐层对比,找出哪些节点需要更新
2.如果节点是组件就看Component diff
3.如果节点是标签就看Element diff

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

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

DOM diff优点

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

DOM diff缺点

DOM diff在同级对比中会有bug,造成页面渲染错误。这个bug的解决如下:
同一层级的一组节点可以通过唯一的id进行区分,可以给节点设定唯一的key值来消除bug。