总体过程中,在Vue首次渲染过后再updateComponent()这个方法中调用了vm._render()和vm._update()这两个方法
在render方法里面它去调用了用户传过来的render函数或者是通过模板编译生成render函数,如果调用了用户传过来的render函数那么接下来调用的是vm.$createElement这个方法(它就是h函数),如果是模板编译成成的render函数它调用的是_c这个方法(它就是h函数),但是不管是哪个方法它最终调用的都是createElement(vm,a,b,c,d,true),在createElement(vm,a,b,c,d,true),它去处理函数传入参数的差异,最终调用的_createElement(),在_createElement()这个方法中最终创建了VNode对象 (vnode = new VNode(config.parsePlatformTagName(tag), data, children, undefined, undefined, context))最终vm._render()结束返回一个vnode,就是VNode对象,返回来的vnode对象交给vm._update()处理
vm._update()这个方法负责把虚拟dom渲染成真实dom,用于渲染的就是调用了vm._patch_方法,在首次执行时,vm._patch_方法传入的第一个参数是vm._$el,传入的是真实dom,如果是数据更新的时候vm._patch_()方法传入的是两个vnode,第一个参数是preVnode也就是上一次渲染时保存的一个vnode
vm._patch_()方法实在runtime/index.js中去初始化的,在初始化的过程中他是在Vue的原型上挂载了_patch_方法 Vue.prototype._patch_ ,所有的实例都可以访问到,这个vm._patch_()方法等同于runtime/patch.js文件夹下导出的patch函数,在这个函数中主要设置了两个对象,分别是modules和nodeOps,modules里存放的是和web平台相关或无关的模板,nodeOps这个对象里面存放的是用来操作dom的,当设置好这两个对象后调用createPatchFunction()函数,在这个函数中最终返回了所需要的的patch函数
patch()函数是在vdom/patch.js中的createPatchFunction()函数中返回的,在createPatchFunction()这个函数中首先做了一个初始化的事情,它的里面定义了特别多的辅助函数,并且去初始化了cbs这个对象,cbs这个对象中存放了所有模块中定义的钩子函数,这些钩子函数的作用是用来处理所有节点的属性/事件/样式等操作,并且在patch()这个函数中还会去判断我们传入的第一个参数是否是真实dom,如果是真实dom的话证明是首次加载这个时候就会把我们的真实dom转化为vnode,然后去调用createElm将newVnode转化为真实dom并挂载到dom树上来,如果是数据更新的时候,新旧节点都是vnode,这时候通过sameVnode这个方法判断新旧vnode是否是相同vnode相同节点,如果是相同节点的话执行patchVnode也就是diff算法,在执行过patchVnode后回去删除旧的节点,在patch()函数中有两个核心函数一个是createElm(vnode,insertVnodeQueue)和patchVnode
这个函数的作用是将虚拟dom转换成真实dom,并且挂载到dom树上,并且它还将虚拟节点的子节点转换成真实dom挂载到dom树,并且触发相应的钩子函数
patchVnode的作用是对比新旧VNode并且对比新旧VNode的子节点,更新差异,如果新旧VNode都有子节点且新旧子节点不同的话,会调用updateChildren对比子节点差异
从头和尾依次找到相同的子节点进行比较patchNode,总共有四种方式 ,如果四种方式都不符合会在老节点的子节点中查找newStartVnode,并进行处理,当循环结束后如果新节点比老节点多,就把新增的子节点插入到DOM中,如果老节点比新节点多,把多余的老节点删除diff算法
// diff 算法
// 更新新旧节点的子节点
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
// removeOnly is a special flag used only by
// to ensure removed elements stay in correct relative positions
// during leaving transitions
const canMove = !removeOnly
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(newCh)
}
// diff 算法
// 当新节点和旧节点都没有遍历完成
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
// oldStartVnode 和 newStartVnode 相同(sameVnode)
// 直接将该 VNode 节点进行 patchVnode
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
// 获取下一组开始节点
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
// 直接将该 VNode 节点进行 patchVnode
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
// 获取下一组结束节点
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
// oldStartVnode 和 newEndVnode 相同(sameVnode)
// 进行 patchVnode,把 oldStartVnode 移动到最后
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
// 移动游标,获取下一组节点
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
// oldEndVnode 和 newStartVnode 相同(sameVnode)
// 进行 patchVnode,把 oldEndVnode 移动到最前面
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
// 以上四种情况都不满足
// newStartNode 依次和旧的节点比较
// 从新的节点开头获取一个,去老节点中查找相同节点
// 先找新开始节点的key和老节点相同的索引,如果没找到再通过sameVnode找
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
// 如果没有找到
if (isUndef(idxInOld)) { // New element
// 创建节点并插入到最前面
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
// 获取要移动的老节点
vnodeToMove = oldCh[idxInOld]
// 如果使用 newStartNode 找到相同的老节点
if (sameVnode(vnodeToMove, newStartVnode)) {
// 执行 patchVnode,并且将找到的旧节点移动到最前面
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// 如果key相同,但是是不同的元素,创建新元素
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
// 当结束时 oldStartIdx > oldEndIdx,旧节点遍历完,但是新节点还没有
if (oldStartIdx > oldEndIdx) {
// 说明新节点比老节点多,把剩下的新节点插入到老的节点后面
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
// 当结束时 newStartIdx > newEndIdx,新节点遍历完,但是旧节点还没有
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
}