讲diff算法前,我们先来理解下什么的虚拟dom把,这样会方便我们对diff算法的理解
什么是vnode ?
virtual Node虚拟节点,无论是组件还是元素,他们最终在vue中表示出来的都是一个个VNode,Vnode的本质是描述dom的javaScript对象。
<div class="title" style="font-size: 30px;color:'hotpink"> hellow</div>
上面一段代码的虚拟节点vnode是什么样呢?
const vnode={
type:'div',
props:{
class:"title",
style:{
"font-size":'30px',
"color":'hotpink'
}
},
children:"hellow"
}
vnode优点:
- 可以跨平台 ,vNode以js对象作为基础,不依赖真实的环境,所以具有跨平台性
- 虚拟DOM不一定可以提高性能,复杂试图下可以提升渲染性能
vdom 虚拟dom:
如果不是只有一个div元素呢,而是有一堆元素,那么此时会形成一个虚拟dom树,
vue通过建立虚拟dom来保持对真实dom的追踪变化
举个栗子:
如果要在一个数组中插入某一个元素,数组a b c d 中在b和c的中间插入一个e元素,vue对有key和无key时的区别
解读:首先对于列表中 abcd是没有变化的,在操作真的dom中,我们只需要插入一个e的li元素
那么vue中对于列表的更新到底是如果操作的呢?
其实vue会根据列表中有key无key调用不同的方法
有key就调用patchKeyedChildren
无key就调用patchUnkeyedChildren方法
先看无key的情况:
事实上c和d其实不需要任何改变,但是因为c被e使用了,导致后续的所有内容都要进行一次改动,并且最后进行新增
无key的源码是这样:
解说:
1.首先拿取旧节点的数组长度和薪节点数组长度进行比较,取值最小的长度
为什么取最小的长度呢?如果取大的长度的length,那么小数组长度去遍历时会发生数组越界,这个很好理解把
c2 = c2 || EMPTY_ARR
const oldLength = c1.length
const newLength = c2.length
const commonLength = Math.min(oldLength, newLength)
let i
for (i = 0; i < commonLength; i++) {
const nextChild = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
patch( //patch可以理解为更新
c1[i],
nextChild,
container,
null,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
2.比较新旧两个节点的长度,如果旧节点长度>新节点长度,那么就删除多余的节点
if (oldLength > newLength) {
// remove old
unmountChildren(
c1,
parentComponent,
parentSuspense,
true,
false,
commonLength
)
}
如果旧节点长度 < 新节点长度,那么就新增节点
else {
// mount new
mountChildren(
c2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
commonLength
)
}
diff算法:比较新旧vnode的过程就是diff算法