虚拟DOM和DOM diff

153 阅读3分钟

虚拟DOM

虚拟DOM是什么

虚拟DOM就是一个普通的JS对象,包含tagpropschildren三个属性,用JS对象的形式来表示一棵真实的DOM树。

优点

减少DOM操作

  • 减少操作次数:虚拟DOM可以将多次操作合并为一次操作。比如添加1000个节点,原生DOM操作1000次,而虚拟DOM只需要操作一次。
  • 减小操作范围:虚拟DOM借助DOM diff,可以把多余的操作省去。比如添加1000个节点,而原本有990个,原生DOM需要添加1000个,虚拟DOM只需要添加10个。

跨平台

虚拟DOM不仅可以变成DOM,还可以变成小程序、iOS和Android应用等,因为虚拟DOM本质是一个对象

缺点

  1. 需要用额外的创建函数,如createElement、h。
  • 改进方法:转译。通过JS来简化成XML写法。
  • 转译的缺点:严重依赖打包工具,需要另外的构建过程。
  1. 规模较大时,React会崩溃,原生DOM速度较快;规模较小时,虚拟DOM速度较快。

DOM diff

DOM diff是什么

DOM diff的主要体现就是一个比较函数,命名为 patch

  • patch函数的主要写法:
patches = patch(oldVNode, newVNode)

//patches 就是要运行的 DOM 操作,可能长这样:
[ 
	{type: 'INSERT', vNode: ... }, 
    {type: 'TEXT',  vNode: ... }, 
    {type: 'PROPS', propsPatch: [...]}
]

优点

减小操作范围:diff 算法仅在两个树的同级的虚拟节点之间做比较,递归地进行比较,最终实现整个 DOM 树的更新。

实现

DOM diff 在做比较时分为三个层级:

  • Tree Diff(层级比较):先进行树结构的层级比较,对同一个父节点下的所有子节点进行比较;接着看节点是什么类型的,是组件就做 Component Diff;如果节点是标签或者元素,就做 Element Diff。
  • Component Diff (组件比较):若组件类型相同,则继续按照层级比较其虚拟 DOM的结构;如果组件类型不同,则替换整个组件的所有内容。
  • Element Diff (元素比较):如果节点是原生标签,则看标签名做比较是否相同来决定替换还是更新属性,然后进入标签后代递归 Tree Diff。

缺点

当 DOM 对同级节点做比较的时候,diff 提供了三个节点操作,分别是:删除、插入和移动。

如以下列表:

const list = [
    {
        id: 1,
        name: 'test1',
    },
    {
        id: 2,
        name: '我被删除了',
    },
    {
        id: 3,
        name: 'test3',
    },
]
<div v-for="(item, index) in list" :key="index" >{{item.name}}</div>

如果使用index作为key,则diff在遍历DOM树时,不能区分每一个分支。此时,如果删去id为2的一项,则id为3的项的index值变成1,即它会成为第二项,造成的效果是删除了原本id为3的数据。此时list:

list = [
    {
        id: 1,
        name: 'test1',
    },
    {
        id: 2,
        name: '我被删除了',
    }
]

解决key问题

最好的办法是使用数组中不会变化的那一项作为key值,对应到项目中,即每条数据都有一个唯一的id,来标识这条数据的唯一性。