虚拟DOM和DOM diff

202 阅读3分钟

虚拟DOM是什么?

虚拟DOM简单来说是一个JavaScript对象,相当于给js和dom之间做了一个缓存,在真正执行dom前,先利用虚拟dom做好各项工作,最后一次性操作dom

<div id="app">
  <p class="text">hello world!!!</p>
</div>

以上HTML转换为虚拟DOM

cosnt node={
  tag: 'div',
  props: {
    id: 'app'
  },
  chidren: [
    {
      tag: 'p',
      props: {
        className: 'text'
      },
      chidren: [
        'hello world!!!'
      ]
    }
  ]
}


React中的虚拟DOM

const VNode={
  key:null,
  props:{
    children:[  //子元素
      {type:'span',...},
      {type:'span',...}
    ],
    className: "red"
    onclick:()=>{}
  },
  ref:null,
  type:"div", //标签名 or 组件名
  ...
}

React中创建虚拟DOM

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

以上简写

<!--通过babel转换为以上形式-->
<div className="red" onClick="{()=>{}}">
  <span>span1</span>
    <span>span1</span>
</div>

Vue中的虚拟DOM

const VNode={
  tag: "div",
  data: {
    class: "red",
    on: {
      click: ()=>{}
    }
  },
  children:[  //子元素
      {tag:'span',...},
      {tag:'span',...}
  ],
  ...
}

Vue中创建虚拟DOM

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

以上简写

<!--通过vue-loader转换为以上形式-->
<div class="red" @click="fn">
  <span>span1</span>
  <span>span2</span>
</div>

虚拟DOM的优点

  1. 将多次操作合并为一个,如添加100个节点只需操作一次
  2. 可以节省多余的操作。如添加1000个节点,其中只有100个是新的,虚拟DOM只会添加新增的节点。

虚拟DOM的缺点

需要额外的创建函数,但是可以简化,如createElement和h,可以通过JSX来简化成XML写法(vue-loader配合vue模板写法)。也因此依赖于打包工具


DOM diff是什么?

是一个函数 changed=patch(oldVNode,newVNode) changed是要运行的dom操作,像这样

[
  {type: 'INSERT', vNode: ... },
  {type: 'TEXT',  vNode: ... },
  {type: 'PROPS', propsPatch: [...]}
]

举例,

<div class="red">
  <span>hello</span>
  <span>world</span>
</div>

若修改第一个span的内容为world,删除第二个span,会产生两棵树,进行tree diff,对两棵树逐层对比:

  1. div没变,不用更新
  2. 第一个span标签不变,更新children内容
  3. 第二个span不见了,删除对应的dom

最后将所有操作记录下来,一次性处理

如果节点是组件就看Component diff

如果是标签就看Element diff

对于组件的diff:

  1. 先看组件类型
  2. 类型不同直接替换,删除旧组件
  3. 类型相同只更新属性,
  4. 然后深入组件做tree diff

对于元素的diff:

  1. 是原生标签,看标签名
  2. 标签名不同直接替换,相同则只看属性
  3. 进入标签进行tree diff

diff存在问题

比如有vue中有一个data数组,array: [1,2,3]

<template>
  <div id="app">
    <Child v-for="item,i in array"        @delete="remove(i)"></Child>
  </div>
</template>

methods:{
  remove(i){
  this.array.splice(i,1)
  }
}

三个元素删除了中间一个,[1,2,3]变成[1,3],看上去仅仅是删除了2,但是按照diff的逻辑,进行新旧dom树的对比:

  1. 首先对比1和1,没问题
  2. 对比2和3,变化了,将2改为3
  3. 对比3和undefined,3不见了,删除3 因此导致明明删除的是2,但实际上删除的却是3,仅仅是把2变成了3

为了避免这一问题,需要在使用v-for时绑定一个key


参考自:

  1. 饥人谷《虚拟DOM和dom diff》
  2. 虚拟DOM到底是什么