虚拟Dom与Dom diff

79 阅读3分钟

虚拟Dom是什么

在原生的JavaScript程序中,我们直接对DOM进行创建和更改,而DOM元素通过我们监听的事件和我们的应用程序进行通讯。

但是vue React会先将你的代码转换成一个JavaScript对象,然后这个JavaScript对象再转换成真实DOM。这个JavaScript对象就是所谓的虚拟DOM

虚拟 Dom 优点

减少 Dom 操作

1、虚拟Dom 可以将多次操作合并为一次操作,比如你添加1000个节点,却是一个接一个操作的(减少频率)

2、虚拟Dom借助Dom diff可以把多余的操作省掉,比如你添加1000个节点,其实只有10个是新增的(减少范围)

跨平台

虚拟Dom 不仅可以变成Dom,还可以变成小程序、ios应用、安卓应用,因为虚拟Dom本质上只是一个js 对象

虚拟DOm长什么样子

react

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

vue

const vNode = {
  tag: "div", // 标签名 or 组件名
  data: {
    class: "red", // 标签上的属性
    on: {
      click: () => {} // 事件
    }
  },
  children: [ // 子元素们
    { tag: "span", ... },
    { tag: "span", ... }
  ],
  ...
}

如何创建虚拟Dom

React.createElement

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

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

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

用JSX 简化创建虚拟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

<div className="red" onClick={fn}>
    <span>span1</span>
    <span>span2</span>
</div>

通过 babel 转为 createElement 形式

Vue Template

<div class="red" @click="fn">
  <span>span1</span>
  <span>span2</span>
</div>

通过 vue-loader 转为 h 形式

虚拟Dom的缺点

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

Dom diff是什么

就是一个函数,我们称之为 patch

patches = patch(oldVNode, newVNode)

patches 就是要运行的 DOM 操作,可能长这样:

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

Dom diff的优点

可以通过比对前后两次生成的虚拟Dom,并把没有改变的部分保留,只更新改变的内容

Dom diff可能的大概逻辑

Tree diff

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

Component diff

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

Element diff

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

DOm diff的缺点

同级别对比存在bug

在进行比较中,如果你没有给予标签唯一的key值,它会以index的形式对标签进行排序,比如有一个数组[1,2,3],我们删除了2,按照我们的逻辑应该是发现了2不见了3没有变,但是Dom diff的逻辑是发现了原本index为1的值更改成了3,而原本index为2的值变成了undefined,这就是Dom diff中的key问题