虚拟 DOM 和 DOM diff

216 阅读3分钟

概念

  • 虚拟DOM
    • 是什么:其实就是个js对象

      虚拟 dom 是相对于浏览器所渲染出来的真实 dom 的,在react,vue等技术出现之前,我们要改变页面展示的内容只能通过遍历查询 dom 树的方式找到需要修改的 dom 然后修改样式行为或者结构,来达到更新 ui 的目的。 这种方式相当消耗计算资源,因为每次查询 dom 几乎都需要遍历整颗 dom 树,如果建立一个与 dom 树对应的虚拟 dom 对象( js 对象),以对象嵌套的方式来表示 dom 树,那么每次 dom 的更改就变成了 js 对象的属性的更改,这样一来就能查找 js 对象的属性变化要比查询 dom 树的性能开销小。

    • 优点:1. 减少不必要的DOM操作 2. 能跨平台渲染

    • 缺点:

    需要额外的创建函数,如 React 中的 createElement 或者 Vue 中的 h可以通过 JSX 来简化成 XML 写法 ,但 是需要额外的构建过程,严重依赖打包工具
  • DOM diff
    • 是什么:一个函数:虚拟DOM的对方算法的一个函数patches=patch(oldVNode,newVNode)
    • 优点:减少DOM操的次数(只需要操作变化的节点)
    • 缺点:同级节点对比存在bug(key)

关于DOM的谣言:DOM操作慢,虚拟操作DOM快?

  • 慢是相比于原生js的api,
  • 但是任何基于DOM库的(Vue/React)都不可能在操作DOM时比DOM快

为什么有谣言呢?

  • 因为确实有时 虚拟DOM快:比如下面的例子

虚拟DOM长什么样子

  1. 在React中的虚拟DOM
const vNode = {
  key: null,
  props: {
    children: [
      {type: 'span', ...},
      {type: 'span', ...}
    ],
    className: "red",
    onClick: ()=>{}
  },
  ref: null,
  type: "div",
  ...
}
  1. 在Vue中的虚拟DOM
const vNode = {
  tag: 'div',
  data: {
    class: 'red',
    on: {
      click: ()=>{}
    }
  },
  children: [
    {tag: "span", ...},
    {tag: "span", ...}
  ],

创建虚拟DOM

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

// 通过 JSX 简化
<div className="X" onClick="{()=> {}}">
    <span>span1</span>
    <span>span2</span>
</div>

//  通过 babel 转为 createElement 形式
  1. Vue
//  只能在 render 函数里得到 h
h('div', {
  class: 'red',
  on: {
    click: () => { }
  },
}, [h('span',{},'span1'), h('span', {}, 'span2'])
    
    
 // 通过简化JSX
<div class="red" @click="fn">
  <span>span1</span>
  <span>span2</span>
</div>

// 通过 vue-loader 转为 h 形式

DOM diff

  1. 把虚拟DOM想象成树形

举例一:

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

当数据变化,x从red变为green

此时DOM diff发现:

  • div标签类型没变,只需要更新div对应的DOM的属性。

  • 子元素没变不更新

举例二:

当数据变化,y从true变为false

DOM diff发现:

  • div没变,不用更新。

  • 子元素1标签没变,但是children变了,更新DOM 内容

  • 子元素2不见了,删除对应的DOM

DOM diff总结:

  • 虚拟 DOM 的对比算法

  • 一个被称为 patch 的函数

  • patches = patch(oldVNode, newVNode)

  • patches 就是要运行的 DOM 操作

      即给两个虚拟节点,返回对应的 DOM 操作
    

DOM diff优点: 1. 能减少不必要的DOM操作

2.能跨平台渲染

DOM diff缺点:

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

  2. 同级节点对比存在bug🔜解决方法:用Key值进行区分

举个栗子

如不加key的三个同级input标签,分别输入不同value:1、2、3,然后点击删除第二个,会发现留下的两个input中,value值是1、2而不是1、3。

点击删除2,你以为2被删除了,结果却是:

因为中间的删除第三个取代第二个的位置这个是我们人类认为的逻辑,计算机认为的逻辑是:第二个的改变,第三个的删除(神奇的算法逻辑)。

如何解决?

  • 用一个唯一的 id(string或者number) 作为 key 进行标识

参考文献:

React 虚拟 Dom 和 diff 算法

Vue2.0 v-for 中 :key 到底有什么用?