我们先打开文件src/core/vdom/patch.js
。代码如下:
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)) {
// 是否存在异步工厂函数,主要是异步组件会使用到,具体的可以看vue的异步组件 ‘https://cn.vuejs.org/v2/guide/components-dynamic-async.html‘
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
if (isDef(data) && isPatchable(vnode)) {
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)) {
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)
}
}
这个函数会对oldVnode
和vnode
进行对比,然后进行DOM更新,下面我们就来仔细看看代码。
if (oldVnode === vnode) {
return
}
首先就是对比新旧两个node节点是否相等,如果相等的话,就return
,直接返回了。
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode)
}
如果虚拟节点的elm
属性存在的话,就说明有被渲染过了,如果ownerArray
存在,说明存在子节点,如果这两点到成立,那就克隆一个vnode节点。
if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key &&(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
1、vnode
是静态节点
2、oldVnode
是静态节点
3、key
属性都相等
4、vnode
属于克隆的虚拟DOM或者是只渲染一次的组件(v-once)
如果满足以上四点,那就赋值一下componentInstance
属性就return了,说明整个组件没有任何变化,还在之前的实例。
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
首先第一了一个data
常量,data上面一般是定义一下属性节点的,attrs
属性、on
事件、directives
指令、props
、hook
钩子(当vnode是组件的时候会有init, prepatch, insert , destroy
四个钩子)。
init
实例化子组件
prepatch
更新子组件
insert
调用子组件的 ’mounted‘生命周期,或者当’keepAlive‘存在的时候触发组件的activated
生命周期
destroy
调用子组件的 ’destroyed‘生命周期,或者当’keepAlive‘存在的时候触发组件的deactivated
生命周期
const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) // 调用一系列的update函数
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
紧接着就是调用各种更新函数。
updateAttrs
// 更新attr属性
updateClass
// 更新class属性
updateDOMListeners
// 更新绑定事件属性
updateDOMProps
// 更新props属性
updateStyle
// 更新style属性
update
// 如果ref属性存在,根据ref属性进行更新
updateDrectives
// 更新Drectives属性
// 是否存在’text‘文本
if (isUndef(vnode.text)) {
//旧的vnode和新的vnode是否存在子元素
if (isDef(oldCh) && isDef(ch)) {
// 如果旧的vnode和新的vnode不相同就调用’updateChildren‘函数更新
if (oldCh !== ch) {
updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
}
} else if (isDef(ch)) {
if (process.env.NODE_ENV !== 'production') {
// 检查是否有重复的key,如果存在重复会提示’warn‘
checkDuplicateKeys(ch)
}
// 如果旧的vnode不存在子集,但是存在’text‘属性,新的vnode存在子集,那就把’Text‘清空。
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)
}
nodeOps存在以下这些方法:
1、createElement
// 创建节点
2、createElementNS
// 创建节点
3、createTextNode
// 创建文本节点
4、createComment
// 创建注释节点
5、insertBefore
// 在节点前面插入
6、removeChild
// 移除子节点
7、appendChild
// 新增子节点
8、parentNode
// 获取父节点
9、nextSibling
// 下一个节点
10、tagName
// 节点名称
11、setTextContent
// 设置节点内容
12、setStyleScope
// 清空节点属性值