手机网站的域名,wordpress标签库 导入,怎么自己免费做网站,wordpress 网站换域名#x1f308;个人主页#xff1a;前端青山 #x1f525;系列专栏#xff1a;Vue篇 #x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue中的diff算法 目录
一、是什么
二、比较方式
三、原理分析
小结 一、是什么
diff 算法是一…
个人主页前端青山 系列专栏Vue篇 人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue中的diff算法 目录
一、是什么
二、比较方式
三、原理分析
小结 一、是什么
diff 算法是一种通过同层的树节点进行比较的高效算法
其有两个特点 比较只会在同层级进行, 不会跨层级比较 在diff比较的过程中循环从两边向中间比较
diff 算法在很多场景下都有应用在 vue 中作用于虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较
二、比较方式
diff整体策略为深度优先同层比较 比较只会在同层级进行, 不会跨层级比较 比较的过程中循环从两边向中间收拢 下面举个vue通过diff算法更新的例子
新旧VNode节点如下图所示 第一次循环后发现旧节点D与新节点D相同直接复用旧节点D作为diff后的第一个真实节点同时旧节点endIndex移动到C新节点的 startIndex 移动到了 C 第二次循环后同样是旧节点的末尾和新节点的开头(都是 C)相同同理diff 后创建了 C 的真实节点插入到第一次创建的 D 节点后面。同时旧节点的 endIndex 移动到了 B新节点的 startIndex 移动到了 E 第三次循环中发现E没有找到这时候只能直接创建新的真实节点 E插入到第二次创建的 C 节点之后。同时新节点的 startIndex 移动到了 A。旧节点的 startIndex 和 endIndex 都保持不动 第四次循环中发现了新旧节点的开头(都是 A)相同于是 diff 后创建了 A 的真实节点插入到前一次创建的 E 节点后面。同时旧节点的 startIndex 移动到了 B新节点的startIndex 移动到了 B 第五次循环中情形同第四次循环一样因此 diff 后创建了 B 真实节点 插入到前一次创建的 A 节点后面。同时旧节点的 startIndex移动到了 C新节点的 startIndex 移动到了 F 新节点的 startIndex 已经大于 endIndex 了需要创建 newStartIdx 和 newEndIdx 之间的所有节点也就是节点F直接创建 F 节点对应的真实节点放到 B 节点后面 三、原理分析
当数据发生改变时set方法会调用Dep.notify通知所有订阅者Watcher订阅者就会调用patch给真实的DOM打补丁更新相应的视图
function patch(oldVnode, vnode, hydrating, removeOnly) {if (isUndef(vnode)) { // 没有新节点直接执行destory钩子函数if (isDef(oldVnode)) invokeDestroyHook(oldVnode)return}
let isInitialPatch falseconst insertedVnodeQueue []
if (isUndef(oldVnode)) {isInitialPatch truecreateElm(vnode, insertedVnodeQueue) // 没有旧节点直接用新节点生成dom元素} else {const isRealElement isDef(oldVnode.nodeType)if (!isRealElement sameVnode(oldVnode, vnode)) {// 判断旧节点和新节点自身一样一致执行patchVnodepatchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)} else {// 否则直接销毁及旧节点根据新节点生成dom元素if (isRealElement) {
if (oldVnode.nodeType 1 oldVnode.hasAttribute(SSR_ATTR)) {oldVnode.removeAttribute(SSR_ATTR)hydrating true}if (isTrue(hydrating)) {if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {invokeInsertHook(vnode, insertedVnodeQueue, true)return oldVnode}}oldVnode emptyNodeAt(oldVnode)}return vnode.elm}}
}
patch函数前两个参数位为oldVnode 和 Vnode 分别代表新的节点和之前的旧节点主要做了四个判断 没有新节点直接触发旧节点的destory钩子 没有旧节点说明是页面刚开始初始化的时候此时根本不需要比较了直接全是新建所以只调用 createElm 旧节点和新节点自身一样通过 sameVnode 判断节点是否一样一样时直接调用 patchVnode去处理这两个节点 旧节点和新节点自身不一样当两个节点不一样的时候直接创建新节点删除旧节点
下面主要讲的是patchVnode部分
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {// 如果新旧节点一致什么都不做if (oldVnode vnode) {return}
// 让vnode.el引用到现在的真实dom当el修改时vnode.el会同步变化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}// 如果新旧都是静态节点并且具有相同的key// 当vnode是克隆节点或是v-once指令控制的节点时只需要把oldVnode.elm和oldVnode.child都复制到vnode上// 也不用再有其他操作if (isTrue(vnode.isStatic) isTrue(oldVnode.isStatic) vnode.key oldVnode.key (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {vnode.componentInstance oldVnode.componentInstancereturn}
let iconst data vnode.dataif (isDef(data) isDef(i data.hook) isDef(i i.prepatch)) {i(oldVnode, vnode)}
const oldCh oldVnode.childrenconst ch vnode.childrenif (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)}// 如果vnode不是文本节点或者注释节点if (isUndef(vnode.text)) {// 并且都有子节点if (isDef(oldCh) isDef(ch)) {// 并且子节点不完全一致则调用updateChildrenif (oldCh ! ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
// 如果只有新的vnode有子节点} else if (isDef(ch)) {if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, )// elm已经引用了老的dom节点在老的dom节点上添加子节点addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
// 如果新vnode没有子节点而vnode有子节点直接删除老的oldCh} else if (isDef(oldCh)) {removeVnodes(elm, oldCh, 0, oldCh.length - 1)
// 如果老节点是文本节点} else if (isDef(oldVnode.text)) {nodeOps.setTextContent(elm, )}
// 如果新vnode和老vnode是文本节点或注释节点// 但是vnode.text ! oldVnode.text时只需要更新vnode.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)}}
patchVnode主要做了几个判断 新节点是否是文本节点如果是则直接更新dom的文本内容为新节点的文本内容 新节点和旧节点如果都有子节点则处理比较更新子节点 只有新节点有子节点旧节点没有那么不用比较了所有节点都是全新的所以直接全部新建就好了新建是指创建出所有新DOM并且添加进父节点 只有旧节点有子节点而新节点没有说明更新后的页面旧节点全部都不见了那么要做的就是把所有的旧节点删除也就是直接把DOM 删除
子节点不完全一致则调用updateChildren
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {let oldStartIdx 0 // 旧头索引let newStartIdx 0 // 新头索引let oldEndIdx oldCh.length - 1 // 旧尾索引let newEndIdx newCh.length - 1 // 新尾索引let oldStartVnode oldCh[0] // oldVnode的第一个childlet oldEndVnode oldCh[oldEndIdx] // oldVnode的最后一个childlet newStartVnode newCh[0] // newVnode的第一个childlet newEndVnode newCh[newEndIdx] // newVnode的最后一个childlet oldKeyToIdx, idxInOld, vnodeToMove, refElm
// removeOnly is a special flag used only by transition-group// to ensure removed elements stay in correct relative positions// during leaving transitionsconst canMove !removeOnly
// 如果oldStartVnode和oldEndVnode重合并且新的也都重合了证明diff完了循环结束while (oldStartIdx oldEndIdx newStartIdx newEndIdx) {// 如果oldVnode的第一个child不存在if (isUndef(oldStartVnode)) {// oldStart索引右移oldStartVnode oldCh[oldStartIdx] // Vnode has been moved left
// 如果oldVnode的最后一个child不存在} else if (isUndef(oldEndVnode)) {// oldEnd索引左移oldEndVnode oldCh[--oldEndIdx]
// oldStartVnode和newStartVnode是同一个节点} else if (sameVnode(oldStartVnode, newStartVnode)) {// patch oldStartVnode和newStartVnode 索引左移继续循环patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)oldStartVnode oldCh[oldStartIdx]newStartVnode newCh[newStartIdx]
// oldEndVnode和newEndVnode是同一个节点} else if (sameVnode(oldEndVnode, newEndVnode)) {// patch oldEndVnode和newEndVnode索引右移继续循环patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)oldEndVnode oldCh[--oldEndIdx]newEndVnode newCh[--newEndIdx]
// oldStartVnode和newEndVnode是同一个节点} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right// patch oldStartVnode和newEndVnodepatchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)// 如果removeOnly是false则将oldStartVnode.eml移动到oldEndVnode.elm之后canMove nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))// oldStart索引右移newEnd索引左移oldStartVnode oldCh[oldStartIdx]newEndVnode newCh[--newEndIdx]
// 如果oldEndVnode和newStartVnode是同一个节点} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left// patch oldEndVnode和newStartVnodepatchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)// 如果removeOnly是false则将oldEndVnode.elm移动到oldStartVnode.elm之前canMove nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)// oldEnd索引左移newStart索引右移oldEndVnode oldCh[--oldEndIdx]newStartVnode newCh[newStartIdx]
// 如果都不匹配} else {if (isUndef(oldKeyToIdx)) oldKeyToIdx createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
// 尝试在oldChildren中寻找和newStartVnode的具有相同的key的VnodeidxInOld isDef(newStartVnode.key)? oldKeyToIdx[newStartVnode.key]: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
// 如果未找到说明newStartVnode是一个新的节点if (isUndef(idxInOld)) { // New element// 创建一个新VnodecreateElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
// 如果找到了和newStartVnodej具有相同的key的Vnode叫vnodeToMove} else {vnodeToMove oldCh[idxInOld]/* istanbul ignore if */if (process.env.NODE_ENV ! production !vnodeToMove) {warn(It seems there are duplicate keys that is causing an update error. Make sure each v-for item has a unique key.)}
// 比较两个具有相同的key的新节点是否是同一个节点//不设keynewCh和oldCh只会进行头尾两端的相互比较设key后除了头尾两端的比较外还会从用key生成的对象oldKeyToIdx中查找匹配的节点所以为节点设置key可以更高效的利用dom。if (sameVnode(vnodeToMove, newStartVnode)) {// patch vnodeToMove和newStartVnodepatchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)// 清除oldCh[idxInOld] undefined// 如果removeOnly是false则将找到的和newStartVnodej具有相同的key的Vnode叫vnodeToMove.elm// 移动到oldStartVnode.elm之前canMove nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
// 如果key相同但是节点不相同则创建一个新的节点} else {// same key but different element. treat as new elementcreateElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)}}
// 右移newStartVnode newCh[newStartIdx]}}
while循环主要处理了以下五种情景 当新老 VNode 节点的 start 相同时直接 patchVnode 同时新老 VNode 节点的开始索引都加 1 当新老 VNode 节点的 end相同时同样直接 patchVnode 同时新老 VNode 节点的结束索引都减 1 当老 VNode 节点的 start 和新 VNode 节点的 end 相同时这时候在 patchVnode 后还需要将当前真实 dom 节点移动到 oldEndVnode 的后面同时老 VNode 节点开始索引加 1新 VNode 节点的结束索引减 1 当老 VNode 节点的 end 和新 VNode 节点的 start 相同时这时候在 patchVnode 后还需要将当前真实 dom 节点移动到 oldStartVnode 的前面同时老 VNode 节点结束索引减 1新 VNode 节点的开始索引加 1 如果都不满足以上四种情形那说明没有相同的节点可以复用则会分为以下两种情况 从旧的 VNode 为 key 值对应 index 序列为 value 值的哈希表中找到与 newStartVnode 一致 key 的旧的 VNode 节点再进行patchVnode同时将这个真实 dom移动到 oldStartVnode 对应的真实 dom 的前面 调用 createElm 创建一个新的 dom 节点放到当前 newStartIdx 的位置
小结 当数据发生改变时订阅者watcher就会调用patch给真实的DOM打补丁 通过isSameVnode进行判断相同则调用patchVnode方法 patchVnode做了以下操作 找到对应的真实dom称为el 如果都有都有文本节点且不相等将el文本节点设置为Vnode的文本节点 如果oldVnode有子节点而VNode没有则删除el子节点 如果oldVnode没有子节点而VNode有则将VNode的子节点真实化后添加到el 如果两者都有子节点则执行updateChildren函数比较子节点 updateChildren主要做了以下操作 设置新旧VNode的头尾指针 新旧头尾指针进行比较循环向中间靠拢根据情况调用patchVnode进行patch重复流程、调用createElem创建一个新节点从哈希表寻找 key一致的VNode 节点再分情况操作