首页 web前端 js教程 在vue中实现虚拟dom的patch(详细教程)

在vue中实现虚拟dom的patch(详细教程)

Jun 02, 2018 am 10:58 AM
patch 教程 详细

这篇文章主要介绍了vue 虚拟dom的patch源码分析,现在分享给大家,也给大家做个参考。

本文介绍了vue 虚拟dom的patch源码分析,分享给大家,具体如下:

源码目录:src/core/vdom/patch.js

 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

    const canMove = !removeOnly

  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { // 开始索引大于结束索引,进不了
   if (isUndef(oldStartVnode)) {
    oldStartVnode = oldCh[++oldStartIdx] // Vnode已经被移走了。
   } else if (isUndef(oldEndVnode)) {
    oldEndVnode = oldCh[--oldEndIdx]
   } else if (sameVnode(oldStartVnode, newStartVnode)) {
    patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
    oldStartVnode = oldCh[++oldStartIdx] // 索引加1。是去对比下一个节点。比如之前start=a[0],那现在start=a[1],改变start的值后再去对比start这个vnode
    newStartVnode = newCh[++newStartIdx]
     
   } else if (sameVnode(oldEndVnode, newEndVnode)) { 
    patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
    oldEndVnode = oldCh[--oldEndIdx]
    newEndVnode = newCh[--newEndIdx]
   } else if (sameVnode(oldStartVnode, newEndVnode)) { 
    patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
    canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))// 把节点b移到树的最右边
    oldStartVnode = oldCh[++oldStartIdx]
    newEndVnode = newCh[--newEndIdx]
     
   } else if (sameVnode(oldEndVnode, newStartVnode)) {  old.end.d=new.start.d
    patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
    canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)// Vnode moved left,把d移到c的左边。=old.start->old.end
    oldEndVnode = oldCh[--oldEndIdx] 
    newStartVnode = newCh[++newStartIdx] 

   } else {
    if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
    idxInOld = isDef(newStartVnode.key)
     ? oldKeyToIdx[newStartVnode.key]
     : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
    if (isUndef(idxInOld)) { 
     createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm) // 创建新节点,后面执行了nodeOps.insertBefore(parent, elm, ref)
    } else {
     vnodeToMove = oldCh[idxInOld]
     /* istanbul ignore if */
     if (process.env.NODE_ENV !== &#39;production&#39; && !vnodeToMove) {
      warn(
       &#39;It seems there are duplicate keys that is causing an update error. &#39; +
       &#39;Make sure each v-for item has a unique key.&#39;
      )
     }
     if (sameVnode(vnodeToMove, newStartVnode)) {
      patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
      oldCh[idxInOld] = undefined
      canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
     } else {
      // same key but different element. treat as new element
      createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
     }
    }
    newStartVnode = newCh[++newStartIdx] 
   
   }
  }
  if (oldStartIdx > oldEndIdx) {
   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  } else if (newStartIdx > newEndIdx) {
   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) // 删除旧的c,removeNode(ch.elm)

  }
 }
登录后复制

function sameVnode (a, b) {
 return (
  a.key === b.key && (
   (
    a.tag === b.tag &&
    a.isComment === b.isComment &&
    isDef(a.data) === isDef(b.data) &&
    sameInputType(a, b)
   ) || (
    isTrue(a.isAsyncPlaceholder) &&
    a.asyncFactory === b.asyncFactory &&
    isUndef(b.asyncFactory.error)
   )
  )
 )
}

/**
   * 比较新旧vnode节点,根据不同的状态对dom做合理的更新操作(添加,移动,删除)整个过程还会依次调用prepatch,update,postpatch等钩子函数,在编译阶段生成的一些静态子树,在这个过程
   * @param oldVnode 中由于不会改变而直接跳过比对,动态子树在比较过程中比较核心的部分就是当新旧vnode同时存在children,通过updateChildren方法对子节点做更新,
   * @param vnode
   * @param insertedVnodeQueue
   * @param removeOnly
   */
 function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
  if (oldVnode === vnode) {
   return
  }

  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
  }

   // 用于静态树的重用元素。
    // 注意,如果vnode是克隆的,我们只做这个。
    // 如果新节点不是克隆的,则表示呈现函数。
    // 由热重加载api重新设置,我们需要进行适当的重新渲染。
  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 (isDef(oldVnode.text)) nodeOps.setTextContent(elm, &#39;&#39;)
    addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
   } else if (isDef(oldCh)) {
    removeVnodes(elm, oldCh, 0, oldCh.length - 1)
   } else if (isDef(oldVnode.text)) {
    nodeOps.setTextContent(elm, &#39;&#39;)
   }
  } 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)
  }
 }

function insertBefore (parentNode, newNode, referenceNode) {
 parentNode.insertBefore(newNode, referenceNode);
}

/**
   *
   * @param vnode根据vnode的数据结构创建真实的dom节点,如果vnode有children则会遍历这些子节点,递归调用createElm方法,
   * @param insertedVnodeQueue记录子节点创建顺序的队列,每创建一个dom元素就会往队列中插入当前的vnode,当整个vnode对象全部转换成为真实的dom 树时,会依次调用这个队列中vnode hook的insert方法
   * @param parentElm
   * @param refElm
   * @param nested
   */

   let inPre = 0
 function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) {
  vnode.isRootInsert = !nested // 过渡进入检查
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
   return
  }

  const data = vnode.data
  const children = vnode.children
  const tag = vnode.tag
  if (isDef(tag)) {
   if (process.env.NODE_ENV !== &#39;production&#39;) {
    if (data && data.pre) {
     inPre++
    }
    if (
     !inPre &&
     !vnode.ns &&
     !(
      config.ignoredElements.length &&
      config.ignoredElements.some(ignore => {
       return isRegExp(ignore)
        ? ignore.test(tag)
        : ignore === tag
      })
     ) &&
     config.isUnknownElement(tag)
    ) {
     warn(
      &#39;Unknown custom element: <&#39; + tag + &#39;> - did you &#39; +
      &#39;register the component correctly? For recursive components, &#39; +
      &#39;make sure to provide the "name" option.&#39;,
      vnode.context
     )
    }
   }
   vnode.elm = vnode.ns
    ? nodeOps.createElementNS(vnode.ns, tag)
    : nodeOps.createElement(tag, vnode)
   setScope(vnode)

   /* istanbul ignore if */
   if (__WEEX__) {
    // in Weex, the default insertion order is parent-first.
    // List items can be optimized to use children-first insertion
    // with append="tree".
    const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
    if (!appendAsTree) {
     if (isDef(data)) {
      invokeCreateHooks(vnode, insertedVnodeQueue)
     }
     insert(parentElm, vnode.elm, refElm)
    }
    createChildren(vnode, children, insertedVnodeQueue)
    if (appendAsTree) {
     if (isDef(data)) {
      invokeCreateHooks(vnode, insertedVnodeQueue)
     }
     insert(parentElm, vnode.elm, refElm)
    }
   } else {
    createChildren(vnode, children, insertedVnodeQueue)
    if (isDef(data)) {
     invokeCreateHooks(vnode, insertedVnodeQueue)
    }
    insert(parentElm, vnode.elm, refElm)
   }

   if (process.env.NODE_ENV !== &#39;production&#39; && data && data.pre) {
    inPre--
   }
  } else if (isTrue(vnode.isComment)) {
   vnode.elm = nodeOps.createComment(vnode.text)
   insert(parentElm, vnode.elm, refElm)
  } else {
   vnode.elm = nodeOps.createTextNode(vnode.text)
   insert(parentElm, vnode.elm, refElm)
  }
 }
function insert (parent, elm, ref) {
  if (isDef(parent)) {
   if (isDef(ref)) {
    if (ref.parentNode === parent) {
     nodeOps.insertBefore(parent, elm, ref)
    }
   } else {
    nodeOps.appendChild(parent, elm)
   }
  }
 }

function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
  for (; startIdx <= endIdx; ++startIdx) {
   const ch = vnodes[startIdx]
   if (isDef(ch)) {
    if (isDef(ch.tag)) {
     removeAndInvokeRemoveHook(ch)
     invokeDestroyHook(ch)
    } else { // Text node
     removeNode(ch.elm)
    }
   }
  }
 }
登录后复制

updateChildren方法主要通过while循环去对比2棵树的子节点来更新dom,通过对比新的来改变旧的,以达到新旧统一的目的。

通过一个例子来模拟一下:

假设有新旧2棵树,树中的子节点分别为a,b,c,d等表示,不同的代号代表不同的vnode,如:

在设置好状态后,我们开始第一遍比较,此时oldStartVnode=a,newStartVnode=a;命中了sameVnode(oldStartVnode,newStartVnode)逻辑,则直接调用patchVnode(oldStartVnode,newStartVnode,insertedVnodeQueue)方法更新节点a,接着把oldStartIdxnewStartIdx索引分别+1,如图:

更新完节点a后,我们开始第2遍比较,此时oldStartVnode=b,newEndVnode=b;命中了sameVnode(oldStartVnode,newEndVnode)逻辑,则调用patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)方法更新节点b,接着调用canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)),把节点b移到树的最右边,最后把oldStartIdx索引+1,newEndIdx索引-1,如图:

更新完节点b后,我们开始第三遍比较,此时oldEndVnode=d,newStartVnode=d;命中了sameVnode(oldEndVnode, newStartVnode)逻辑,则调用patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)方法更新节点d,接着调用canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm),把d移到c的左边。最后把oldEndIdx索引-1,newStartIdx索引+1,如图:

更新完d后,我们开始第4遍比较,此时newStartVnode=e,节点e在旧树里是没有的,因此应该被作为一个新的元素插入,调用createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm),后面执行了nodeOps.insertBefore(parent, elm, ref)方法把e插入到c之前,接着把newStartIdx索引+1,如图:

插入节点e后,我们可以看到newStartIdx已经大于newEndIdx了,while循环已经完毕。接着调用removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) 删除旧的c,最终如图:

updateChildren通过以上几步操作完成了旧树子节点的更新,实际上只用了比较小的dom操作,在性能上有所提升,并且当子节点越复杂,这种提升效果越明显。vnode通过patch方法生成dom后,会调用mounted hook,至此,整个vue实例就创建完成了,当这个vue实例的watcher观察到数据变化时,会两次调用render方法生成新的vnode,接着调用patch方法对比新旧vnode来更新dom.

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

JQuery选中select组件被选中的值方法

vue.js中$set与数组更新方法_vue.js

vue与vue-i18n结合实现后台数据的多语言切换方法

以上是在vue中实现虚拟dom的patch(详细教程)的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

得物使用教程 得物使用教程 Mar 21, 2024 pm 01:40 PM

得物APP是当前十分火爆品牌购物的软件,但是多数的用户不知道得物APP中功能如何的使用,下方会整理最详细的使用教程攻略,接下来就是小编为用户带来的得物多功能使用教程汇总,感兴趣的用户快来一起看看吧!得物使用教程【2024-03-20】得物分期购怎么使用【2024-03-20】得物优惠券怎么获得【2024-03-20】得物人工客服怎么找【2024-03-20】得物取件码怎么查看【2024-03-20】得物求购在哪里看【2024-03-20】得物vip怎么开【2024-03-20】得物怎么申请退换货

夸克浏览器使用教程 夸克浏览器使用教程 Feb 24, 2024 pm 04:10 PM

夸克浏览器是当前十分火爆的一款多功能的浏览器,但是多数的小伙伴不知道夸克浏览器如何使用其中的功能,下方会整理出来最使用的功能技巧,接下来就是小编为用户带来的夸克浏览器多功能使用教程汇总,感兴趣的用户快来一起看看吧!夸克浏览器使用教程【2024-01-09】:夸克如何扫描试卷看答案【2024-01-09】:夸克浏览器怎么开启成人模式【2024-01-09】:如何删除夸克已用空间【2024-01-09】:怎么清理夸克网盘存储空间【2024-01-09】:夸克怎么取消备份【2024-01-09】:夸克

升级numpy版本:详细易学的指南 升级numpy版本:详细易学的指南 Feb 25, 2024 pm 11:39 PM

如何升级numpy版本:简单易懂的教程,需要具体代码示例引言:NumPy是一个重要的Python库,用于科学计算。它提供了一个强大的多维数组对象和一系列与之相关的函数,可用于进行高效的数值运算。随着新版本的发布,不断有更新的特性和Bug修复可供我们使用。本文将介绍如何升级已安装的NumPy库,以获取最新特性并解决已知问题。步骤1:检查当前NumPy版本在开始

夏天,一定要试试拍摄彩虹 夏天,一定要试试拍摄彩虹 Jul 21, 2024 pm 05:16 PM

夏天雨后,经常能见到一种美丽且神奇的特殊天气景象——彩虹。这也是摄影中可遇而不可求的难得景象,非常出片。彩虹出现有这样几个条件:一是空气中有充足的水滴,二是太阳以较低的角度进行照射。所以下午雨过天晴后的一段时间内,是最容易看到彩虹的时候。不过彩虹的形成受天气、光线等条件的影响较大,因此一般只会持续一小段时间,而最佳观赏、拍摄时间更为短暂。那么遇到彩虹,怎样才能合理将其记录下来并拍出质感呢?1.寻找彩虹除了上面提到的条件外,彩虹通常出现在阳光照射的方向,即如果太阳由西向东照射,彩虹更有可能出现在东

微信关收款声音的步骤教程 微信关收款声音的步骤教程 Mar 26, 2024 am 08:30 AM

1、首先打开微信。2、点击右上角【+】。3、点击二维码收款。4、点击右上角三个小点。5、点击关闭收款到账语音提醒。

DisplayX(显示器测试软件)使用教程 DisplayX(显示器测试软件)使用教程 Mar 04, 2024 pm 04:00 PM

在购买显示器的时候对其进行测试是必不可少的一环,能够避免买到有损坏的,今天小编教大家来使用软件对显示器进行测试。方法步骤1.首先要在本站搜索下载DisplayX这款软件,安装打开,会看到提供给用户很多种检测方法。2.用户点击常规完全测试,首先是测试显示器的亮度,用户调节显示器使得方框都能看得清。3.之后点击鼠标即可进入下一环节,如果显示器能够分清每个黑色白色区域那说明显示器还是不错的。4.再次单击鼠标左键,会看到显示器的灰度测试,颜色过渡越平滑表示显示器越好。5.另外在displayx软件中我们

photoshopcs5是什么软件?-photoshopcs5使用教程 photoshopcs5是什么软件?-photoshopcs5使用教程 Mar 19, 2024 am 09:04 AM

PhotoshopCS是PhotoshopCreativeSuite的缩写,由Adobe公司出品的软件,被广泛用于平面设计和图像处理,作为新手学习PS,今天就让小编为您解答一下photoshopcs5是什么软件以及photoshopcs5使用教程。一、photoshopcs5是什么软件AdobePhotoshopCS5Extended是电影、视频和多媒体领域的专业人士,使用3D和动画的图形和Web设计人员,以及工程和科学领域的专业人士的理想选择。呈现3D图像并将它合并到2D复合图像中。轻松编辑视

专家教你!华为手机截长图的正确方法 专家教你!华为手机截长图的正确方法 Mar 22, 2024 pm 12:21 PM

随着智能手机的不断发展,手机的功能也变得越来越强大,其中截长图功能成为了许多用户日常使用的重要功能之一。截长图可以帮助用户将一个较长的网页、对话记录或者图片一次性保存下来,方便查阅和分享。而在众多手机品牌中,华为手机也是备受用户推崇的一款品牌之一,其截长图功能也备受好评。本文将为大家介绍华为手机截长图的正确方法,以及一些专家技巧,帮助大家更好地利用华为手机的

See all articles