Dom Diff初探

3,033 阅读4分钟
查看本文Dom Diff源码demo地址

一、前言

1、什么是Dom Diff( 本质 )?

比对(diff)渲染更新前后产生的两个虚拟dom对象的差异,并产出差异补丁对象,再将差异补丁对象应用到真实dom节点上

2、为什么需要Dom Diff?

大家应该都知道操作Dom代价是昂贵的,因为操作Dom其本质是两个线程(JS引擎和GUI渲染引擎)间发送指令(通信)的过程,并且浏览器在创建初始化一个元素时,会为其创建很多属性,因此,在大量操作Dom的场景下,通过一些计算来尽可能少地操作Dom,保证了性能的下限。当然Dom Diff不一定更快!

3、Diff算法思想的现状?

React、Vue等js库中,甚至Flutter的视图更新中都有应用


二、Virtual Dom

一句话概述:用JavaScript的树形结构对象描述真实dom结构

1、@babel/preset-react

在我们日常开发中,我们写的react代码会经过@babel/preset-react(babel7)编译成如下图1


从上图可以看出整个React代码编译生成Virtual-Dom的过程,相信你已经明白什么是虚拟Dom了


三、VirtualDom从初次渲染到更新

1、过程分析(图2)


(1)  用JS对象模拟DOM(虚拟DOM1

(2)  将此虚拟DOM1转成真实DOM并插入页面中(render

(3)  如果有事件发生(用户操作更新数据)修改了虚拟DOM产生虚拟DOM2比较两棵虚拟              DOM树的差异,得到差异对象diff

(4)  把差异对象应用到真正的DOM树上(patch

下面具体分析每一步:


2、生成VirtualDom1

从图一我们可以看出通过babel编译生成的代码,最终是通过createElement函数去构造每一个节点的:

(1) 通过构造函数Element构造虚拟dom节点



(2) 通过createElement来生成Element构造函数的实力对象(虚拟dom对象)


至此,我们就得到了VirtualDom1对象


3、 将VirtualDom1转化为真实dom(render)


思路:
(1)  根据虚拟dom对象中的type属性,使用document.createElement创建对应元素A

(2)  遍历虚拟dom对象中的props属性,先判断props是否null,再使用for in遍历props对象并           设置属性到元素A( setAttr方法 )

(3)  遍历虚拟dom对象中的children属性,判断当前遍历项是否继承Element构造函数,是的           话,递归当前遍历项,否则创建文本节点,并通过appenChild添加到元素A的子节点上

至此我们成功通过递归VirtualDom1创建出对应的真实dom


4、将真实dom挂载到指定根节点(renderDom)



5、Dom-Diff

由于外部用户操作导致生成了VirtualDom2,然后比对Virtual-dom1和Virtual-dom2的差异

分析:接收两个虚拟Dom对象,通过深度优先遍历来进行比对(只比较同级节点,不跨级比较),每次遍历到一个节点,就记录一个索引值(从0递增),如果比对发现有差异,就把索引值对应需要执行的操作存起来

比对规则

(1)  节点类型不同,就执行节点替换模式{ type:  REPLACE, newNode }

(2)  节点相同,就比较属性是否相同,不相同则{ type: ATTR, attrs: { class: 'list-group'  } }

(3)  新的节点不存在,则{ type: REMOVE }

(4)  如果当前节点都是字符串且不相等,则是文本的变化{ type: TEXT, text: 'xxxx' }


过程分析(图3)


由上图我们可以看出,标红0,2,4,6的节点发生了改变,那么产出的pathes补丁对象是:

图4


6、打补丁(将补丁对象应用到真实dom上)

分析:我们通过VirtualDom1生成真实dom,所以VirtualDom1和真实dom的结构是一一对应的,然后又因为补丁对象是通过对VirtualDom1进行深度优先遍历生成的,那么我们只要对真实dom进行深度优先遍历,那补丁对象中记录的索引(表示节点位置)就能和真实dom对应上了,然后就能取出对应需要执行的dom操作

图5


至此就完成了DOM的更新操作


四、简易源码实现

Dom-Diff的简易实现源码:  https://github.com/DBCdouble/Dom-Diff


附:后期补录关于key的Diff规则