您现在的位置是:主页 > news > 可以做商品砍价的网站/台湾搜索引擎

可以做商品砍价的网站/台湾搜索引擎

admin2025/5/15 12:37:53news

简介可以做商品砍价的网站,台湾搜索引擎,帝国cms和wordpress,上海网站建设框架图原文链接我的blog,欢迎STAR。接着上一篇,我们继续来讲Vue的Virtual Dom diff 算法中的patchVnode方法,以及核心updateChildren方法。 在上篇中,我们谈到,当vnode不为真实节点,且vnode与oldVnode为同一节点…

可以做商品砍价的网站,台湾搜索引擎,帝国cms和wordpress,上海网站建设框架图原文链接我的blog,欢迎STAR。接着上一篇,我们继续来讲Vue的Virtual Dom diff 算法中的patchVnode方法,以及核心updateChildren方法。 在上篇中,我们谈到,当vnode不为真实节点,且vnode与oldVnode为同一节点…

原文链接我的blog,欢迎STAR。

接着上一篇,我们继续来讲Vue的Virtual Dom diff 算法中的patchVnode方法,以及核心updateChildren方法。


在上篇中,我们谈到,当vnode不为真实节点,且vnode与oldVnode为同一节点时,会调用patchVnode方法。
我们直接从源码上进行分析:

  // patchVnode()有四个参数// oldVnode: 旧的虚拟节点// vnode: 新的虚拟节点// insertedVnodeQueue:  存在于整个patch中,用于收集patch中插入的vnode;// removeOnly: 这个在源码里有提到,removeOnly is a special flag used only by<transition-group>也就是说是特殊的flag,用于transition-group组件。function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {// 如果oldVnode与vnode为同一引用, 不进行任何处理。if (oldVnode === vnode) {return}// 如果不为同一引用,那说用新的vnode创建了。// 如果vnode, oldVnode都为静态节点,且vnode.key === oldVnode.key相等时,当vnode为克隆节点,或者vnode有v-once指令时,只需把oldVnode对应的真实dom,以及组件实例都复制到vnode上。if (isTrue(vnode.isStatic) &&isTrue(oldVnode.isStatic) &&vnode.key === oldVnode.key &&(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {vnode.elm = oldVnode.elmvnode.componentInstance = oldVnode.componentInstancereturn// 在进行下一步操作之前会调用prepatch hook,但是这个是vnode在data里定义的prepatch hook,并不是全局定义的prepatch hooklet iconst data = vnode.dataif (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {i(oldVnode, vnode)}// 让vnode引用到现在的真实DOM,当elm修改的时候,会同步修改vnode.elmconst elm = vnode.elm = oldVnode.elmconst oldCh = oldVnode.childrenconst ch = vnode.children// 我们先patchVnode, 方法就是先调用全局的update hook// 然后调用data里定义的update hookif (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.text未定义// 这里有个值得注意的地方,具有text属性的vnode不应该具备有children// 对于<p>abc<i>123</i></p>的写法应该是// h('p', ['abc', h('i', '123')])// 而不是, h('p', 'abc', [h('i', '123')])// 因此,对text存在与否的情况需单独拿出来分析if (isUndef(vnode.text)) {// 如果oldVnode与vnode都存在childrenif (isDef(oldCh) && isDef(ch)) {// 如果两个children 不相同,调用updateChildren()方法更新子节点的操作。(接下来将讲解)if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)} else if (isDef(ch)) {// 如果只有vnode.children 存在// 当oldVnode.text不为空,vnode.text未定义时,清空elm.textContent// 添加vnode.childrenif (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)} else if (isDef(oldCh)) {// 如果只有oldVnode.children存在,移除oldVnode.childrenremoveVnodes(elm, oldCh, 0, oldCh.length - 1)} else if (isDef(oldVnode.text)) {// 同上,如果oldVnode.text存在,vnode.text不存在,清空elm.textContentnodeOps.setTextContent(elm, '')}} else if (oldVnode.text !== vnode.text) {// 如果vnode.text存在(vnode是一个text node),且不等于oldVnode.text// 更新elm.textContentnodeOps.setTextContent(elm, vnode.text)}// 最后再调用 postpatch hook。if (isDef(data)) {if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)}}

接着说重点 当oldVnode.children与vnode.children都存在,且不相同时调用的updateChildren()方法, 同样的,咱们从源码上分析:

 // updateChildren(),有五个参数// parentElm: oldVnode.elm 的引用// oldCh, newCh: 分别是上面分析中的oldVnode.children, vnode.children// insertedVnodeQueue, removeOnly 请参考上面。function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {let oldStartIdx = 0let newStartIdx = 0let oldEndIdx = oldCh.length - 1let oldStartVnode = oldCh[0]let oldEndVnode = oldCh[oldEndIdx]let newEndIdx = newCh.length - 1let newStartVnode = newCh[0]let newEndVnode = newCh[newEndIdx]let oldKeyToIdx, idxInOld, elmToMove, 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// 遍历过程共有5种情况// 比较判断的依据是,sameVnode(),值不得值得比较。// key,tag(当前节点标签名),isComment(是否是注释节点)// data,节点的数据对象是否都存在或都不存在// (a, b)=> {//   return (//        a.key === b.key &&//        a.tag === b.tag &&//        a.isComment === b.isComment &&//        isDef(a.data) === isDef(b.data) &&//        sameInput(a, b)//    )// }// 当oldStartIndex > oldEndIdx 或者 newStartIndex > newEndIdx, 停止遍历。while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {// 对于vnode.key的比较,会把oldVnode = nullif (isUndef(oldStartVnode)) {oldStartVnode = oldCh[++oldStartIdx]// 同上oldEndVnode = oldCh[--oldEndIdx]} else if (sameVnode(oldStartVnode, newStartVnode)) {// 第一种情况:// 从oldCh与newCh的第一个开始,逐步往后遍历。// 如果oldStartVnode与newStartVnode值得比较,// 执行pathchVnode()方法// oldStartVnode, newStartVnode相对位置不变。patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)oldStartVnode = oldCh[++oldStartIdx]newStartVnode = newCh[++newStartIdx]} else if (sameVnode(oldEndVnode, newEndVnode)) {// 第二种情况:// 从oldCh与newCh的最后一个开始,逐步往前遍历。// 如果oldEndVnode,newEndVnode值得比较// 执行pathchVnode()// oldEndVnode, newEndVnode相对位置不变patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)oldEndVnode = oldCh[--oldEndIdx]newEndVnode = newCh[--newEndIdx]} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right// 第三种情况:// 从oldCh的第一个,newCh的最后一个开始,oldCh往后,newCh往前遍历,// 如果oldStartVnode与newEndVnode值得比较// 此时需要把oldStartVnode放到oldEndVnode后面// oldCh往后,newCh往前patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))oldStartVnode = oldCh[++oldStartIdx]newEndVnode = newCh[--newEndIdx]} else if (sameVnode(oldEndVnode, newStartVnode)) {// 第四种情况:// 从oldCh的最后一个,newCh的第一个,oldCh往前,newCh往后,遍历。// 如果oldEndVnode与newStartVnode值得比较// 此时需要把oldEndVnode放到oldStartVnode前边// oldCh往前,newCh往后patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)oldEndVnode = oldCh[--oldEndIdx]newStartVnode = newCh[++newStartIdx]} else {// 第五种情况:// 使用key比较// 首先会调用createKytoOldIdx()方法,产生一个key-index对象列表// 然后根据这个表来进行更改if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)// 如果newStartVnode.key存在,根据key来找到对应的index,命名为idxInOld // 如果不存在,设置为nullidxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : nullif (isUndef(idxInOld)) {// 如果idxInOld不存在时,此时是一个新的vnode// 将这个vnode插入到oldStartVnode.elm 的前边// 把newStartVnode设置为下一个节点createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)newStartVnode = newCh[++newStartIdx]} else {// 如果idxInOld存在时,那么对应的oldVnode存在// 根据index,找到oldVnode对应的childrenelmToMove = oldCh[idxInOld]// 如果不是生产环境,且elmToMove不存在// 此时因为idxInOld已经存在,而oldCh[idxInOld]不存在// 只有可能keys重复了/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && !elmToMove) {warn('It seems there are duplicate keys that is causing an update error. ' +'Make sure each v-for item has a unique key.')}// 如果根据vnode.key找出的elmToMove与newStartVnode值得比较比较// patchVnode这两个节点// 之后,需要把这个child设置为undefined// 同时需要把oldStartVnode.elm的位置移到newStartVnode.elm之前,以免影响接下来的遍历。if (sameVnode(elmToMove, newStartVnode)) {patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)oldCh[idxInOld] = undefinedcanMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)newStartVnode = newCh[++newStartIdx]**重点内容**} else {// same key but different element. treat as new element// 如果不值得比较,此时key已经相同,说明是tag不同,或者其他不同,此时创建一个新节点// 将这个vnode插入到oldStartVnode.elm 的前边// 把newStartVnode设置为下一个节点createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)newStartVnode = newCh[++newStartIdx]}}}}// 遍历完成之后,存在两种情况// 如果 oldStartIdx > oldEndIdx, 即oldCh先遍历完// 位于 newStartIdx与newEndIdx之间的节点都可认为是新的节点if (oldStartIdx > oldEndIdx) {refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elmaddVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)} else if (newStartIdx > newEndIdx) {// 如果newStartIdx > newEndIdx, 即newCh先遍历完// 此时,位于oldStartIdx与oldEndIdx之间的节点已经不存在了// 调用removeVnodes()方法移除节点。removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)}}

直接在源码上分析,可能有点乱,总结一下:

patchVnode共有以下情况:

  • 如果oldVnodevnode引用完全一致,则可以认为没有变化,无需进行任何操作。

  • 如果vnode, oldVnode都为静态节点,且vnode.key === oldVnode.key相等时,当vnode为克隆节点,或者vnodev-once指令时,只需把oldVnode对应的真实dom,以及组件实例都复制到vnode上。

  • 如果vnode不是text node:

    • 如果vnode.childrenoldVnode.children都存在,调用updateChildren()方法。

    • vnode.children存在,oldVnode.children不存在时,添加vnode.children

    • vnode.children不存在,oldVnode.children存在时,需要移除oldVnode.children

    • 当两者的children都不存在时,如果oldVnodetext node,则需清空elm.textContent

  • 如果vnodetext node,改变elm.textContent

patchVnode有一个值得注意的地方是,vdom中规定,具有text属性的vnode不应该具备children,因此需把text node单独拿出来分析。
_

updateChildren()方法共有5种比较方式,前四种无key的情况,后一种为有key的情况,当oldStartIdx > oldEndIdx或者newStartIdx > newOldStartIdx的时候停止遍历。

引用推荐的那篇文章图:

遍历示意图

  • 第一种比较方式从oldChnewCh各自第一个vnode开始比较,当值得比较时,调用上述中的patchVnode方法进行比较, 同时将oldChnewCh的下一个vnode分别设为oldStartVnodenewStartVnode(比较的相对位置不变), startVnode既是开始比较的vnode

  • 第二种比较方式从oldChnewCh各自最后一个vnode开始比较,当值得比较时,调用上述中的patchVnode方法进行比较,同时将oldChnewCh的上一个vnode分别设置为oldEndVnodenewEndVnode(比较的相对位置不变),, endVnode既是结束比较的vnode

  • 第三种比较方式,从oldCh的第一个vnodenewCh的最后一个vnode开始比较,当值得比较时,调用上述中的patchVnode方法比较,同时将oldCh的下一个vnode设置为oldStartVnode,将newCh的上一个vnode设置为newEndVnode,并且此时说明oldStartVnode.elm向右移动,并且已经移动到oldEndVnode.elm的后边了,调用相应的方法移动位置。

  • 第四种比较方式,从oldCh的最后一个vnodenewCh的第一个vnode开始比较,当值得比较时,调用上述中的patchVnode方法比较,同时将oldCh的上一个vnode设置为oldEndVnode,将newCh的上一个vnode设置为newStartVnode,并且此时说明oldEndVnode.elm向左移动,并且已经移动到oldStartVnode.elm的前边了,调用相应的方法移动位置。

  • 第五种,使用key比较,先会产生一个key-index表,然后判断vnode.key存在与否?

    • 如果不存在,是一个新的vnode,将这个vnode插入到oldStartVnode.elm 的前边,并且把newStartVnode设置为下一个节点。

    • 如果存在,那么对应的oldVnode应该存在,此时可以根据key来找到对应的vnode,然后判断这个vnodenewStartVnode是否值得比较?

      • 当值得比较时,调用patchVnode,并且需要把这个child设置为undefined,同时需要把oldStartVnode.elm的位置移到newStartVnode.elm之前,以免影响接下来的遍历。

      • 如果不值得比较,此时key已经相同,说明是tag不同,或者其他不同,此时创建一个新节点将这个vnode插入到oldStartVnode.elm 的前边。

遍历完成后,如果oldCh先遍历完,位于newStartIdx与newEndIdx之间的节点都可认为是新的节点,调用相应的方法插入节点。如果newCh先遍历完,此时,位于oldStartIdx与oldEndIdx之间的节点已经不存在了,调用removeVnodes()方法移除节点。


结语

码完这篇历时四天的文章,我的身心是崩溃的,期间查阅了相当多的资料,来确保表达的准确性(当然其中还有一些错误的地方,如有发现,请指出。),推荐从关于一些Vue的文章。(2),开始阅读,算是逐渐深入吧,从render,template,el => vnode => diff算法。如果各位同学喜欢,麻烦点个赞。谢谢。

完。