vue diff算法和react的diff算法,vue的diff算法和react的diff

  vue diff算法和react的diff算法,vue的diff算法和react的diff

  diff算法的本质是找出两个对象的区别,目的是尽可能地重用节点。下面这篇文章主要介绍Vue中diff算法的相关信息,有需要的朋友可以参考一下。

  

目录

  概述

  虚拟dom(虚拟Dom)

  原则

  实施程序

  方法补丁

  SameVnode函数

  PatchVnode函数

  更新孩子函数

  标签

  

概述

  Diff算法可以说是Vue比较核心的一个内容。之前Vue只是用于一些开发,具体的核心内容其实涉及的并不多。最近刚看到这个内容。先简单说一下Vue2.0的diff算法的实现,具体从实现的几个功能来分析。

  

虚拟Dom(virtual dom)

  虚拟DOM是提取真实的DOM数据,以对象的形式模拟树形结构。

  例如,下面是我们真正的DOM

  差异

  p1234/p

  差异

  span1111/span

  /div

  /div

  从真实DOM生成的虚拟DOM如下

  var Vnode={

  标签:“div”,

  儿童:[

  {

  标签:“p”,

  正文:“1234”

  },

  {

  标签:“div”,

  儿童:[

  {

  标签:“span”,

  文本:“1111”

  }

  ]

  }

  ]

  }

  

原理

  diff的原理是当前的真实dom生成一个虚拟DOM,即虚拟DOM。当虚拟DOM中某个节点的数据发生变化时,会生成一个新的Vnode。然后,将这个Vnode与oldVnode进行比较,发现不一样,就直接在真实的DOM上进行修改。

  

实现过程

  Patch是diff算法实现过程的核心,其中patchVnode、sameVnode和updateChildren方法值得我们关注,下面依次讲解。

  

patch方法

  patch的核心逻辑是比较两个Vnode节点,然后将差异更新到视图中。比较的方法是对等比较,而不是每一级的循环遍历。如果比较后有差异,将这些差异更新到视图中。下面是一个比较方法的例子。

  

sameVnode函数

  sameVnode的作用是判断两个节点是否相同。判断相同的依据是键值、tag(标签)、isCommit(注释)、输入的类型是否相同等。这种方法有一些缺陷。面对v-for下的键值使用索引的情况,也可能判断为可复用节点。

  建议不要使用index作为键值。

  

patchVnode函数

  //传入几个参数,oldVnode表示旧节点,Vnode表示新节点,readOnly表示只读节点。

  函数patchVnode(

  奥尔德夫诺德,

  vnode,

  insertedVnodeQueue,

  ownerArray,

  索引,

  仅移除

  ) {

  If (oldVnode===vnode) {//当旧节点和新节点一致时,不需要比较,返回

  返回

  }

  if(isDef(vnode . elm)isDef(owner array)){

  //克隆重用的vnode

  vnode=owner array[index]=cloneVNode(vnode)

  }

  const elm=vnode . elm=old vnode . elm

  if(is true(old vnode . isasyncplaceholder)){

  if(isDef(vnode . async factory . resolved)){

  水合物(oldVnode.elm,Vnode,insertedVnodeQueue)

  }否则{

  vnode.isAsyncPlaceholder=true

  }

  返回

  }

  //重用静态树的元素

  //如果克隆了vnode,我们只会这样做

  //如果新节点没有被克隆,说明渲染函数已经被克隆了。

  //由hot-reload-api重置,我们需要进行适当的重新渲染。

  if (isTrue(vnode.isStatic)

  isTrue(oldVnode.isStatic)

  vnode.key===oldVnode.key

  (is true(vnode . is cloned) is true(vnode . is once))

  ) {

  vnode . component instance=old vnode . component instance

  返回

  }

  让我

  常数数据=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)is patchable(vnode)){

  for(I=0;I CBS . update . length;cbs.update[i](oldVnode,Vnode)

  if(isDef(I=数据。hook)isDef(I=I . update))I(旧vnode,Vnode)

  }

  if (isUndef(vnode.text)) {

  if (isDef(oldCh) isDef(ch)) {

  如果(oldCh!==ch) updateChildren(elm,oldCh,ch,insertedVnodeQueue,removeOnly)

  } else if (isDef(ch)) {

  if (process.env.NODE_ENV!==生产){

  检查重复密钥

  }

  if(isDef(旧vnode。text))nodeops。settext内容(elm,)

  addVnodes(elm,null,ch,0,ch.length - 1,insertedVnodeQueue)

  } else if (isDef(oldCh)) {

  移除节点(旧ch,0,旧Ch.length - 1)

  } else if (isDef(oldVnode.text)) {

  nodeOps.setTextContent(elm,)

  }

  } else if (oldVnode.text!==vnode.text) {

  nodeOps.setTextContent(elm,vnode.text)

  }

  if (isDef(data)) {

  if(isDef(I=数据。hook)isDef(I=I . post patch))I(旧vnode,Vnode)

  }

  }

  具体的实现逻辑是:

  新旧节点一样的时候,不需要做改变,直接返回

  如果新旧都是静态节点,并且具有相同的钥匙,当虚拟节点是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elm和oldVnode.child都复制到虚拟节点上

  判断虚拟节点是否是注释节点或者文本节点,从而做出以下处理

  当虚拟节点是文本节点或者注释节点的时候,当vnode.text!==oldVnode.text的时候,只需要更新虚拟节点的文本内容;

  奥尔德夫诺德和vndoe都有子节点,如果子节点不相同,就调用更新孩子方法,具体咋实现,下文有

  如果只有虚拟节点有子节点,判断环境,如果不是生产环境,调用检查重复密钥方法,判断键值是否重复。之后在奥尔德夫诺德上添加当前的荣誉勋爵

  如果只有奥尔德夫诺德上有子节点,那就调用方法删除当前的节点

  

updateChildren函数

  更新孩子,顾名思义,就是更新子节点的方法,从以上的patchVnode的方法,可以看出,当新旧节点都有子节点的时候,会执行这个方法。下面我们来了解下它的实现逻辑,也会有一些大家可能有看到过类似的示例图,先看下代码

  函数updateChildren (parentElm,oldCh,newCh,insertedVnodeQueue,removeOnly) {

  设oldStartIdx=0

  设newStartIdx=0

  设oldEndIdx=oldCh.length - 1

  设oldStartVnode=oldCh[0]

  设oldEndVnode=oldCh[oldEndIdx]

  设newEndIdx=newCh.length - 1

  设newStartVnode=newCh[0]

  设newEndVnode=newCh[newEndIdx]

  let oldKeyToIdx,idxInOld,vnodeToMove,refElm

  //只移除是仅由过渡组使用的特殊标志

  //确保移除的元素保持在正确的相对位置

  //在离开过渡期间

  const canMove=!仅移除

  if (process.env.NODE_ENV!==生产){

  checkDuplicateKeys(newCh)

  }

  while(old startidx=olden didx newStartIdx=newEndIdx){

  if (isUndef(oldStartVnode)) {

  oldStartVnode=old ch[old startidx]//Vnode已向左移动

  } else if (isUndef(oldEndVnode)) {

  oldEndVnode=oldCh[ - oldEndIdx]

  } else if(sameVnode(oldStartVnode,newStartVnode)) {

  patchVnode(oldStartVnode,newStartVnode,insertedVnodeQueue,newCh,newStartIdx)

  oldStartVnode=old ch[old startidx]

  newStartVnode=newCh[newStartIdx]

  } else if (sameVnode(oldEndVnode,newEndVnode)) {

  patchVnode(oldEndVnode,newEndVnode,insertedVnodeQueue,newCh,newEndIdx)

  oldEndVnode=oldCh[ - oldEndIdx]

  newEndVnode=newCh[ - newEndIdx]

  } else if(sameVnode(oldStartVnode,newEndVnode)) { //Vnode右移

  patchVnode(oldStartVnode,newEndVnode,insertedVnodeQueue,newCh,newEndIdx)

  可以移动节点操作。在(父elm,oldStartVnode.elm,nodeops。下一个兄弟(oldendvnode。榆树))

  oldStartVnode=old ch[old startidx]

  newEndVnode=newCh[ - newEndIdx]

  } else if (sameVnode(oldEndVnode,newStartVnode)) { //Vnode左移

  patchVnode(oldEndVnode,newStartVnode,insertedVnodeQueue,newCh,newStartIdx)

  can move nodeops . insert before(parent elm,oldEndVnode.elm,oldStartVnode.elm)

  oldEndVnode=oldCh[ - oldEndIdx]

  newStartVnode=newCh[newStartIdx]

  }否则{

  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,false,newCh,newStartIdx)

  }否则{

  vnodeToMove=oldCh[idxInOld]

  if (sameVnode(vnodeToMove,newStartVnode)) {

  patchVnode(vnodeToMove,newStartVnode,insertedVnodeQueue,newCh,newStartIdx)

  oldCh[idxInOld]=未定义

  can move nodeops . insert before(parent elm,vnodeToMove.elm,oldStartVnode.elm)

  }否则{

  //相同的键但不同的元素。视为新元素

  createElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm,false,newCh,newStartIdx)

  }

  }

  newStartVnode=newCh[newStartIdx]

  }

  }

  if (oldStartIdx oldEndIdx) {

  re felm=is undef(newCh[newEndIdx 1])?null : newCh[newEndIdx 1]。榆树

  addVnodes(parentElm,refElm,newCh,newStartIdx,newEndIdx,insertedVnodeQueue)

  } else if (newStartIdx newEndIdx) {

  remove nodes(old ch,oldStartIdx,oldEndIdx)

  }

  }

  这里我们先定义几个参数,oldStartIdx(旧节点头索引),oldEndIdx(旧节点尾索引),oldStartVnode(旧节点头元素),oldEndVnode(旧节点尾元素);同样,newStartIdx等四项是新的节点头索引等。

  再看while循环中的操作,这也是核心内容。

  判断是同一个节点后,节点还需要继续patchVnode方法。

  如果旧的头元素和新的头元素是相同的节点,则旧的头索引和新的头索引同时向右移位。

  如果旧尾元素和新尾元素是同一个节点,则旧尾索引和新尾索引同时左移。

  如果旧的头元素点与新的尾元素是同一个节点,根据方法上传的readonly判断,如果为false,那么将旧的头元素移动到旧节点的尾索引的最后一位,旧的头索引向右移动,新的尾索引向左移动。

  如果旧的尾元素点与新的头元素是同一个节点,根据方法上传的readonly判断,如果为false,则将旧的尾元素移动到旧节点的头索引前面,旧的尾索引左移,新的头索引右移。

  如果以上都不匹配

  确定是否存在与oldCh中的newStartVnode具有相同关键字的Vnode。如果不是,说明是新节点。创建一个新节点并插入它。

  如果发现一个Vnode与newStartVnode具有相同的键,则将其命名为VnodeToMove,然后与newStartVnode进行比较。如果相同,请再次转到patchVnode。如果removeOnly为false,则将名为vnodeToMove.elm的vnode与newStartVnode的键相同,移动到oldStartVnode.elm的前面

  如果键值相同,但节点不同,则创建一个新节点。

  While循环后,如果新节点数组或旧节点数组中有剩余节点,根据具体情况删除或添加。

  oldStartIdx oldEndIdx时,表示先遍历oldCh,表示有新的节点冗余,增加新的节点。

  newStartIdx newEndIdx时,表示先遍历新节点,还剩下一些旧节点,所以删除剩下的节点。

  让我们看一下示例图。

  原始节点(oldVnode是旧节点,Vnode是新节点,diff是最后一次diff算法后生成的节点数组)

  在循环中第一次,这里我们发现旧的尾元素和新的头元素是一致的,于是旧的尾元素D移到了旧的头索引的前面,也就是a的前面,同时旧的尾索引左移,新的头索引右移。

  在第二个循环中,新的头元素与旧的头元素一致。此时两个元素不动,新旧头索引同时向右移动。

  第三个循环后,发现旧元素中没有与当前元素相同的节点,于是添加,F放在旧的第一个元素之前。同样,第四个周期是一致的,两个周期后生成新的示例图。

  第五次循环,就像第二次循环一样。

  在第六个周期,newStartIdx再次向右移动。

  7.最后一次移动后,newStartIdx newEndIdx已经退出了while循环,证明newCh的第一次遍历完成,oldCh存在冗余节点。冗余节点直接删除,所以最后出来的节点。

  以上是diff算法相关的函数以及diff算法的实现过程。

  

结语

  Diff算法是虚拟DOM的核心部分。同层对比,通过新旧节点的对比,将变化的地方更新为真实的DOM。

  实现方法有patch、patchVnode和updateChildren。

  patch的核心是,如果新节点有,旧节点没有,就添加;旧节点有,新节点没有,删除;如果存在,判断是否相同,如果相同,调用patchVnode进行下一次比较。

  patchVnode的核心是:如果新旧节点都不是注释或文本节点,新节点有子节点,而旧节点没有子节点,则添加子节点;如果新节点没有子节点,而旧节点有子节点,则删除旧节点下的子节点;如果两者都有子节点,则调用updateChildren方法。

  updateChildren的核心是比较新旧节点,并对其进行添加、删除或更新。

  这里只是对Vue2.0的diff算法做一个初步的说明,更深层次的原理以及Vue3.0的diff算法有没有变化都需要学习。

  关于Vue中diff算法的这篇文章到此为止。关于Vue的diff算法的更多信息,请搜索我们之前的文章或者继续浏览下面的相关文章。希望大家以后能多多支持我们!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: