必要性:https://github.com/vuejs/vue/blob/dev/src/core/instance/lifecycle.js
mountComponent()方法
Vue中Watcher力度的降低,一个组件对应一个Watcher,为了明确的知道在更新的过程中到底谁发生了变化,所以我们必须使用diff
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
执行方式:https://github.com/vuejs/vue/blob/dev/src/core/vdom/patch.js
patchVnode ()方法
patchVnode是diff发生的地方,整体策略:深度优先,同层比较
diff算法
function patchVnode (
oldVnode,
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
) {
if (oldVnode === vnode) {
return
}
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode)
}
const elm = vnode.elm = oldVnode.elm
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
// reuse element for static trees.
// note we only do this if the vnode is cloned -
// if the new node is not cloned it means the render functions have been
// reset by the hot-reload-api and we need to do a proper re-render.
// 静态节点判断
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
// 执行一些组件钩子
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
// 查找新旧节点是否存在子级
const oldCh = oldVnode.children
const ch = vnode.children
// 属性更新 <div style="color:blue"> <div style="color:red">
if (isDef(data) && isPatchable(vnode)) {
// cbs中关于属性更新的数据拿出来[arrtFn,classFn...]
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
// 判断是否元素
if (isUndef(vnode.text)) {
// 双方都有子级
if (isDef(oldCh) && isDef(ch)) {
// 比子级 reorder
// 递归操作
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
// 新节点有子级
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(ch)
}
// 清空老节点文本
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
// 创建子级并追加
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
// 老节点有子级删除即可
removeVnodes(oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
// 老节点存在文本清空
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) {
// 双方都是文本节点,更新文本
nodeOps.setTextContent(elm, vnode.text)
}
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
高效性:patch.js - updateChildren()方法
测试代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue虚拟DOM</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="demo">
<h1>虚拟DOM</h1>
<p>foo</p>
</div>
<script>
let vm = new Vue({
el:"#demo",
data:{foo:'foo'},
mounted() {
setTimeout(() => {
this.foo = 'f00000';
},1000);
}
})
</script>
</body>
</html>
总结:
1.diff算法是虚拟DOM技术的必然产物;通过新旧虚拟DOM做对比(即diff),将变化的地方更新在真是DOM上;另外,也需要diff高效的执行对比过程,从而降低时间复杂度。
2.vue中diff执行的时刻是组件实例执行其更新函数时,它会比对上一次渲染结果oldVnode和新的渲染结果newVnode,此过程成为patch。
3.diff过程整体遵循深度优先,同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或文本节点做不同操作;比较两组子节点是算法的重点,首先假设头尾节点可能相同做4次对比尝试,如果没有找到相同节点才按照通用方式遍历查找,查找结束在按情况处理剩下的节点;借助key通常可以精确找到相同节点,因此整个patch过程非常高效。