在前面的博客中,我浅薄的学习了Vue 源码中 的 diff 以及 对 key 的使用,如今 再来学习一下更加难以理解的 patch 的过程javascript
vue 在 虚拟 dom 这一块,是参照了 snabbdom.js 而后在上面进行了部分的修改的,因此若是有不理解的,推荐先去学习这一个东西vue
- 在这一系列的函数里面,有不少的钩子函数,相似于 destory 和 create 、insert 的 钩子函数,事实上 这些 钩子函数 用户使用的时候是无知觉的,由于 这个是虚拟 dom 的组件的 钩子函数,相似于 style 组件,事件 on 组件,这些 钩子函数
在 patch 函数里,调用了 核心函数 patchVnode 函数,也正是 这个函数,调用了 diff :updateChildren 函数java
主要函数判断了老节点是否存在,而后执行销毁或者建立,而后执行 patchVnodenode
// 用于 比较 新老节点的不一样,而后更新的 函数 function patch (oldVnode, vnode, hydrating, removeOnly) { // 当新节点不存在的时候,销毁旧节点 if (isUndef(vnode)) { if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return } let isInitialPatch = false const insertedVnodeQueue = [] // 若是旧节点 是未定义的,直接建立新节点 if (isUndef(oldVnode)) { isInitialPatch = true createElm(vnode, insertedVnodeQueue) } else { const isRealElement = isDef(oldVnode.nodeType) // 当老节点不是真实的 dom 节点, 当两个节点是相同节点的时候,进入 patctVnode 的过程 // 而 patchVnode 也是 传说中 diff updateChildren 的调用者 if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch existing root node patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly) } else { // 当老节点是真实存在的 dom 节点的时候 if (isRealElement) { // 当 老节点是 真实节点,而是在 ssr 环境的时候,修改 SSR_ATTR 属性 if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { oldVnode.removeAttribute(SSR_ATTR) hydrating = true } .... // 设置 oldVnode 为一个包含 oldVnode 的无属性节点 oldVnode = emptyNodeAt(oldVnode) } // replacing existing element const oldElm = oldVnode.elm // 获取父亲节点,这样方便 删除或者增长节点 const parentElm = nodeOps.parentNode(oldElm) // 在 dom 中插入新节点 createElm( vnode, insertedVnodeQueue, oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) // 递归 更新父占位符元素 // 就是执行一遍 父节点的 destory 和 create 、insert 的 钩子函数 // 事实上 这些 钩子函数 用户使用的时候是无知觉的,由于 这个是虚拟 dom 的组件的 钩子函数 // 相似于 style 组件,事件组件,这些 钩子函数 if (isDef(vnode.parent)) { let ancestor = vnode.parent const patchable = isPatchable(vnode) while (ancestor) { for (let i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](ancestor) } ancestor.elm = vnode.elm if (patchable) { for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, ancestor) } const insert = ancestor.data.hook.insert if (insert.merged) { for (let i = 1; i < insert.fns.length; i++) { insert.fns[i]() } } } else { registerRef(ancestor) } ancestor = ancestor.parent } } // 销毁老节点 if (isDef(parentElm)) { removeVnodes([oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { // 触发老节点 的 destory 钩子 invokeDestroyHook(oldVnode) } } } // 执行 虚拟 dom 的 insert 钩子函数 invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) // 返回最新 vnode 的 elm ,也就是真实的 dom节点 return vnode.elm }
patchVnode 函数是对比 两个虚拟 dom 不一样的地方, 同时 也是 递归 调用 updateChildren 的 函数数组
- 首先看是不是同一个节点、是不是 注释节点、是不是 异步函数组件、是不是静态节点,是不是 once 的节点,是的话,不执行
- 是否存在子节点,而后新老节点的子节点是否相同,否就执行 updateChildren 函数深刻对比
- 不然就查看新老节点是否存在,新节点存在可是老节点不存在,增长,新节点不存在可是老节点存在,删除
- 检查 新老节点的 text 属性是否一致,否的话,就更新
function patchVnode ( oldVnode, // 旧节点 vnode, // 新节点 insertedVnodeQueue, // 插入节点的队列 ownerArray, // 节点 数组 index, // 当前 节点的 removeOnly // 只有在 patch 函数中被传入,当老节点不是真实的 dom 节点,当新老节点是相同节点的时候 ) { // 若是新节点和旧节点 相等(使用了 同一个地址,直接返回不进行修改) // 这里就是 不少人 没有设置 key 以后,发现某一个模块没有被改变的缘由 if (oldVnode === vnode) { return } if (isDef(vnode.elm) && isDef(ownerArray)) { // clone reused vnode vnode = ownerArray[index] = cloneVNode(vnode) } const elm = vnode.elm = oldVnode.elm // 当 当前节点 是 注释节点(被 v-if )了,或者是一个 异步函数节点,那不执行 if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue) } else { vnode.isAsyncPlaceholder = true } return } // 当前节点 是一个静态节点的时候,或者 标记了 once 的时候,那不执行 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 // 调用 prepatch 的钩子函数 if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode) } const oldCh = oldVnode.children const ch = vnode.children // 调用 update 钩子函数 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) } // 新节点 没有 text 属性 if (isUndef(vnode.text)) { // 若是都有子节点,对比更新子节点 if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) } else if (isDef(ch)) { // 新节点存在,可是老节点不存在 // 若是老节点是 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)) { // 若是老节点是 text, 清空 nodeOps.setTextContent(elm, '') } // 新旧节点 text 属性不同 } else if (oldVnode.text !== vnode.text) { // 将 text 设置为 新节点的 text nodeOps.setTextContent(elm, vnode.text) } if (isDef(data)) { // 执行 postpatch 钩子函数 if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) } }