dom-diff简易版实现
一、创建虚拟dom
利用 create-react-app
快速创建一个项目模板;
删掉src下的源文件,替换成 index.js
首先我们先要用一个对象定义一个虚拟DOM的数据结构:
Element {
type: 'ul',
props: {
class: 'list'
},
children: [
Element{
type: 'li',
props: {
class: 'item'
},
children: ['a']
}
]
}
复制代码
开始码代码实现虚拟dom的方法实现。
浏览器上查看打印的日志信息,如下:既然虚拟DOM方法已经写好,下一步就要将这个虚拟dom插入到页面中,那我们可以专门写一个渲染真实节点的方法render
先遍历最外层ul
的type
和props
两个属性
注意:input
标签的value
属性 还有所有标签的style
属性
好了,接下来就是继续遍历children
属性,此时children
会有两种情况
- 如果是文本 直接插入;
- 如果是子元素,递归遍历直到最终的结果是文本;
下一步我们将这个实际的DOM元素结构插入到页面中
完成第一部分。
二、实现dom-diff算法
dom-diff
算法就是在两棵抽象语法树的同一位置采用先序的深度遍历算法做比较,同时用补丁的形式记录需要更新的节点位置。
若type
不一致直接替换当前节点以及当前节点下的子节点;
如果两个父节点一致,则从左往后遍历子节点,若子节点一致,遍历子节点下的子节点,依次递归。
补丁包的定义规则如下:
1. 属性不同(type: 'ATTRS', attrs)
2. 新的节点被删除了 (type: 'REMOVE', index: xxxx)
3. 节点类型不同/新增 (type: 'REPLACE', newNode)
4. 仅仅是文本变化(type: 'TEXT', text)
复制代码
新建一个dom-diff.js
,专门处理diff
算法
手动调用diff
方法(react中调用diff
算法是在触发setState
之后)
两个虚拟dom结构如下:
先处理type
相同,属性不同的情况。
发现控制台已经打印到属性变化的补丁包,最后我们把属性的小补丁包存放到最外层的大补丁包中
// 补丁包 存放两个虚拟dom的差异部分
let patchs = {}
// 放到最外层的大补丁包中
if (currentPatchs.length > 0) {
patchs[index] = currentPatchs
}
复制代码
好了 相同类型的父节点一样,在属性比较完成之后,就需要比较children
的属性是否有变化
比较children
属性内部元素是否变化,利用递归去遍历
let globalIndex = 0
function diffChildren (oldChildrens, newChildrens) {
oldChildrens.forEach((child, idx) => {
walk(child, newChildrens[idx], ++globalIndex)
})
}
复制代码
如果一开始type
类型不相同不需要再去比较,直接用新节点替换老节点即可;
// type不一致
currentPatchs.push({
type: TYPES.REPLACE,
newNode: newTree
})
复制代码
兼容并处理好各种情况,比如:新节点不存在的情况,新节点增加,新节点类型改变,新节点文本改变以及新节点的属性变化等情况;
最终拿到所有与旧节点有差异的对象放入patchs这样的一个补丁对象中。
补丁包的key
就是对应新节点有变化的数据位置。
三、 打补丁更新视图
最后一步将补丁的差异对象与现有虚拟DOM节点遍历进行一一比较与替换。
根据之前定义的不同补丁对象结构依次处理
大功告成!
这只是diff算法的一个简易实现,还存在一些复杂情况处理的情况以及还有很多算法上面优化的方案,不过已经让我们大概了解了diff
算法的原理。
如有笔误或者其他实现不对的地方,还望大家指出,谢谢!
具体代码可以参考github链接查看:dom-diff-demo