今天我们来总结一下有关于虚拟 DOM 和 DOM diff 的相关知识点。
首先 DOM 相信大家都不会陌生,就是文档对象模型,这是 JS 中的一个重要的概念。那么虚拟 DOM 又是什么呢?
虚拟 DOM
虚拟 DOM 是一种能够表示 DOM 树的对象,通常会含有签名、标签上的属性、事件监听和子元素等。
它有如下的优点:
- 能够减少 DOM 操作。虚拟 DOM 能将多次操作合并成一次操作,以此达到优化操作次数的目的
- 优化操作范围。虚拟 DOM 能够借助 DOM diff 把多余的操作省略掉,以此达到优化操作范围,选出实际需要操作的对象的目的
- 跨平台操作。虚拟 DOM 不单止可以操作普通的 HTML 元素,更可以变成 DOM、小程序等
- 可以通过 JSX 语法来简化成 XML 语法(需要额外的构建工具)
同时也有以下缺点:
- 如果规模超过一定范围,那么稳定性会较原生 DOM 更差
- 需要额外创建函数。比如 createElement 或者 h() 等
下面分别给出两个例子:
- 在 React 中:
const vNode = {
key: null,
props:{
children:[ //子元素们
{type: 'span',...},
{type: 'span',...}
],
className: "red" //标签上的属性
onClick: ()=>{} //事件
},
ref: null,
type:"div", //标签名 or 组件名
...
}
//创建虚拟dom
createElement('div',{className:'red',onClick:()=>{}},
[
createElement('span',{},'span1')
createElement('span',{},'span2')
]
)
//JSX简化,通过 babel 转为 createElement的形式
<div className="red" onClick={()=>{}}>
<span>span1</span>
<span>span2</span>
</div>
- 在 Vue 中:
const vNode = {
tag:"div", //标签名 or 组件名
children:[
//子元素们
{type: 'span',...},
{type: 'span',...}
],
data:{
class: "red" //标签上的属性
on:{
click: ()=>{}
} //事件
},
...
}
//创建虚拟dom
h('div',{
class: 'red',
on:{
click:()=>{}
},
},[h('span',{},'span1'),h('span',{},'span2')])
//Template简化,通过vue-loader 转为h形式
h('div',{
class: 'red',
on:{
click:()=>{}
},
},[h('span',{},'span1'),h('span',{},'span2')])
DOM diff
DOM diff 其实就是虚拟 DOM 的对比算法,会对比两颗虚拟 DOM 树的所有节点,并生成一个 patch,用来局部更新真实的 DOM
其中,patches = patch(oldVNode, newVNode)
它最重要的作用,就是找出新旧两个节点或 DOM 树的不同,然后对不通的节点进行操作,实际的 DOM 操作可能是:
[
{type: 'INSERT', vNode:...},
{type: 'TEXT', vNode:...},
{type: 'PROPS', propsPatch:[...]},
]
那么要如何确切的理解呢?
我们可以把虚拟 DOM 想象成一个树形:
如果我们只改变根节点的 div 的 class 属性的话,那么 DOM diff 就会发现只有 div 的 class 改变了,那么他就只会修改这一点,两个 span 和里面的内容都不会被改变
但是如果我们删掉图中的第二个 span,那么奇怪的事情就发生了,DOM diff 会认为是把第一个 span 的内容修改成第二个 span 的内容,然后删掉第二个 span,也就是变成这样:
那么可以稍微理解成,DOM diff 可能有如下三种逻辑:
- three diff
-
将新旧两棵树对比,找出需要更新的节点
-
如果节点是组件,就看Component diff
-
如果节点是标签,就看 Element diff
- Component diff
-
如果节点是节点组件,就先看组件类型
-
类型不同直接替换,删掉旧的
-
类型相同则只更新属性
-
然后深入组件做Three diff(递归)
- Element diff
-
如果节点是原生标签,则看标签名
-
标签名不同直接替换,相同则只更新属性
-
进入标签后再做Three diff(递归)
但是 DOM diff 也有缺点,那就是如果是同级比较的话,就会存在 bug,详情可以参考方应杭举的非常生动的例子。
那么要解决这个 bug,就需要在每个变量后面加上对应的 key,而且千万不能是 index!