GVKun编程网logo

Myers diff算法与Hunt-McIlroy算法

22

在这篇文章中,我们将带领您了解Myersdiff算法与Hunt-McIlroy算法的全貌,同时,我们还将为您介绍有关Diff算法、diff算法使用技巧总结、diff算法使用详解、diff算法如何使用的

在这篇文章中,我们将带领您了解Myers diff算法与Hunt-McIlroy算法的全貌,同时,我们还将为您介绍有关Diff算法、diff算法使用技巧总结、diff算法使用详解、diff算法如何使用的知识,以帮助您更好地理解这个主题。

本文目录一览:

Myers diff算法与Hunt-McIlroy算法

Myers diff算法与Hunt-McIlroy算法

最长的常见子序列问题是经典的计算机科学问题,解决该问题的算法是版本控制系统和Wiki引擎的根本。有两种基本算法:用于创建原始版本的Hunt-McIlroy算法diff和当前由GNUdiff实用程序使用的Myersdiff算法。通过
找到表示两个字符串 或文本文件 之间的编辑空间的图形的最短路径,
两者似乎或多或少地起作用。编辑空间是将一个序列转换为另一个序列所需的插入或删除次数。那么Myer的diff算法和Hunt-McIlroy算法到底有什么区别?

答案1

小编典典
  • 迈尔斯算法* 是“分而治之算法”:它的工作原理是寻找递归两个序列的中央配以最小的编辑脚本。完成此操作后,只会记住匹配项,然后再次递归比较之前和之后的两个子序列,直到没有其他可比较的为止。通过尽可能地匹配子序列的末端来找到中心匹配项,并且在不可能的情况下,将编辑脚本增加1,扫描每个对角线到达的最远位置,然后查看多远匹配可以进行,如果匹配遇到另一端的匹配,则算法只是找到了中心匹配。这种方法的优点是占用O(m + n)内存,并在 O(nd)中 执行 *,d具有编辑脚本的复杂性。

  • 亨特和麦克罗伊 接近整个文件索引他们的计算匹配,进入所谓的K-候选人。k是LCS长度。通过找到属于适当纵坐标内的匹配项(遵循其论文中解释的规则),逐步增强LCS。在执行此操作时,将记住每条路径。这种方法的问题在于它做的工作比必要的多:它会记住所有路径,最坏的情况是 O(mn) 内存,时间复杂度是 o(mn log m)

Myers算法之所以能胜出,是因为它在工作时不会记住路径,也不需要“预见”要去的地方,因此它可以随时仅专注于使用最小复杂度的编辑脚本可以到达的最远位置。
。这种“最小的复杂性”可确保找到的是LCS,并且不会记住找到匹配项所经过的路径,直到找到匹配项时才使用更少的内存。不尝试提前计算所有匹配项将避免它们的存储,并使它们在匹配时受益,而不是浪费内存。

Diff算法

Diff算法

Diff

  • diff对比的就是vnode
  • 同时由于dom很少跨级移动,所以对比只在同层级中进行
  • vue和react的diff算法大体是一样的

VNode

  • vue的vnode如下
{
    el:  div  //对真实的节点的引用,本例中就是document.querySelector(''#id.classA'')
    tagName: ''DIV'',   //节点的标签
    sel: ''div#v.classA''  //节点的选择器
    data: null,       // 一个存储节点属性的对象,对应节点的el[prop]属性,例如onclick , style
    children: [], //存储子节点的数组,每个子节点也是vnode结构
    text: null,    //如果是文本节点,对应文本节点的textContent,否则为null
}
  • react的vnode如下
{
    type: ''div'',
    props: {
        className: ''myDiv'',
    },
    chidren: [
        { type: ''p'', props: { value:''1'' } },
        { type: ''div'', props: { value:''2'' } },
        { type: ''span'', props: { value:''3'' } }
    ]
}

Vue

  • vue的diff是边比较边更新的过程
  • 更改数据时,触发试图更新,会生成一棵新的vdom树
  • 对比同级的vnode,仅当vue认为两个vnode值得比较时,才会继续对比其子节点,如果认为不值得比较,会直接删除旧节点,插入新节点
function patch (oldVnode, vnode) {
    if (sameVnode(oldVnode, vnode)) {
        patchVnode(oldVnode, vnode)
    } else {
        const oEl = oldVnode.el
        let parentEle = api.parentNode(oEl)
        createEle(vnode)
        if (parentEle !== null) {
            api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl))
            api.removeChild(parentEle, oldVnode.el)
            oldVnode = null
        }
    }
    return vnode
}

function sameVnode(oldVnode, vnode){
  // 两节点key值相同,并且sel属性值相同,即认为两节点属同一类型,可进行下一步比较
    return vnode.key === oldVnode.key && vnode.sel === oldVnode.sel
}
  • 针对同类型的节点,进行patch操作,并比较他的子节点
patchVnode (oldVnode, vnode) {
    const el = vnode.el = oldVnode.el  //让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化。
    let i, oldCh = oldVnode.children, ch = vnode.children
    if (oldVnode === vnode) return  //新旧节点引用一致,认为没有变化
    //文本节点的比较
    if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
        api.setTextContent(el, vnode.text)
    }else {
        updateEle(el, vnode, oldVnode)
        //对于拥有子节点(两者的子节点不同)的两个节点,调用updateChildren
        if (oldCh && ch && oldCh !== ch) {
            updateChildren(el, oldCh, ch)
        } else if (ch){  //只有新节点有子节点,添加新的子节点
            createEle(vnode) //create el''s children dom
        } else if (oldCh){  //只有旧节点内存在子节点,执行删除子节点操作
            api.removeChildren(el)
        }
    }
}
  • 针对双方都拥有子节点的情况,通过updateChildren来处理更新
updateChildren (parentElm, oldCh, newCh) {
    let oldStartIdx = 0, 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
    let idxInOld
    let elmToMove
    let before
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
            if (oldStartVnode == null) {   //对于vnode.key的比较,会把oldVnode = null
                oldStartVnode = oldCh[++oldStartIdx] 
            } else if (oldEndVnode == null) {
                oldEndVnode = oldCh[--oldEndIdx]
            } else if (newStartVnode == null) {
                newStartVnode = newCh[++newStartIdx]
            } else if (newEndVnode == null) {
                newEndVnode = newCh[--newEndIdx]
            } else if (sameVnode(oldStartVnode, newStartVnode)) {
                patchVnode(oldStartVnode, newStartVnode)
                oldStartVnode = oldCh[++oldStartIdx]
                newStartVnode = newCh[++newStartIdx]
            } else if (sameVnode(oldEndVnode, newEndVnode)) {
                patchVnode(oldEndVnode, newEndVnode)
                oldEndVnode = oldCh[--oldEndIdx]
                newEndVnode = newCh[--newEndIdx]
            } else if (sameVnode(oldStartVnode, newEndVnode)) {
                patchVnode(oldStartVnode, newEndVnode)
                api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))
                oldStartVnode = oldCh[++oldStartIdx]
                newEndVnode = newCh[--newEndIdx]
            } else if (sameVnode(oldEndVnode, newStartVnode)) {
                patchVnode(oldEndVnode, newStartVnode)
                api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el)
                oldEndVnode = oldCh[--oldEndIdx]
                newStartVnode = newCh[++newStartIdx]
            } else {
               // 使用key时的比较
                if (oldKeyToIdx === undefined) {
                    oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表
                }
                idxInOld = oldKeyToIdx[newStartVnode.key]
                if (!idxInOld) {
                    api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
                    newStartVnode = newCh[++newStartIdx]
                }
                else {
                    elmToMove = oldCh[idxInOld]
                    if (elmToMove.sel !== newStartVnode.sel) {
                        api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
                    }else {
                        patchVnode(elmToMove, newStartVnode)
                        oldCh[idxInOld] = null
                        api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)
                    }
                    newStartVnode = newCh[++newStartIdx]
                }
            }
        }
        if (oldStartIdx > oldEndIdx) {
            before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el
            addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
        } else if (newStartIdx > newEndIdx) {
            removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
        }
}
  • 概况下,就是分别新建4个指针,指向新旧节点的头尾,然后通过两两比对,看是否是同一个类型的节点
  • 然后分情况,如果新旧头节点是同一个类型节点,直接调用patchVnode即可,同理新旧尾节点是同一个类型节点,也是patchVnode处理
  • 但如果判断新头和旧尾是同一个类型节点,除了要patchVnode处理外,还需要将尾部节点移到头部
  • 同理,如果新尾和旧头是同一个类型节点,patchVnode处理后,将头节点移到尾部
  • 如果四种比较都没有击中,这时判断新头的节点是否在仍未被对比的旧节点中,如果在,patchVnode处理,并在dom中将旧节点移到新头的位置,原本旧节点对应的Vnode置空,这样下次判断就会被跳过,而如果新节点不在旧节点列表里,则直接新建节点插入即可
  • 最后,当头指针和尾指针相遇并错过后,判断当前四个指针的位置,如果旧的头尾指针中间还有节点,那这些节点都是多余的,需要被移除,如果新的头尾指针中间还有节点,那这些节点都是新增的,需要被插入

React

  • react的diff是先比较,后更新的流程,对比阶段会先记录下差异
function diff (oldTree, newTree) {
  // 当前节点的标志,以后每遍历到一个节点,加1
  var index = 0
  var patches = {} // 用来记录每个节点差异的对象
  dfsWalk(oldTree, newTree, index, patches)
  return patches
}

// 对两棵树进行深度优先遍历
function dfsWalk (oldNode, newNode, index, patches) {
  // 对比oldNode和newNode的不同,记录下来,省略了代码...
  patches[index] = [...]

  diffChildren(oldNode.children, newNode.children, index, patches)
}
  • 先对比当前节点,记录差异,然后对比子节点
  • 子节点的对比直接从左节点开始,判断其是否在旧节点列表中,如果不在,则记录新建,如果在,则标记移动
  • 最后根据差异统一更新dom

区别

  • vue的diff是边比较边更新的,react则是先记录差异,再统一更新
  • vue认为两个节点元素是同一个元素是根据key以及sel来判断的,如果元素类型相同,但classname不一致的话也会认为是不同的元素,而react是根据元素类型以及key来判断的,所以会认为是同一个元素类型,直接在这上面进行属性增减
  • diff策略也不一样,vue是新旧头尾四个节点对比,react则是从左到右进行对比
  • 另外react16提出了fiber这个概念,diff是基于fiber树进行的,且可以被打断,让浏览器能处理更高优先级的任务,而vue的diff和patch是不能被打断的

key的作用

  • vue和react的key作用是一样的,就是告诉引擎当前对比的两个节点是否是同一个节点
  • vue没有key的时候,两个都是undefined,则vue会认为其是相同节点,而就地复用这个元素,但很可能这是个input元素,你已经在里面输入了某些信息,而更新时不会移动这些信息导致最终结果可能不是你所期望的,所以需要指明key来防止元素的复用
  • react则会默认赋值给key,所以子元素中,新旧节点的位置可能发生了移动,但因为react默认index作为key,还是会认为相同位置上的元素是同一个元素,而在其基础上修改属性,导致发生和vue一样的问题

diff算法使用技巧总结

diff算法使用技巧总结

这次给大家带来diff算法使用技巧总结,diff算法使用的注意事项有哪些,下面就是实战案例,一起来看一下。

虚拟dom

diff算法首先要明确一个概念就是diff的对象是虚拟dom,更新真实dom则是diff算法的结果

Vnode基类

 constructor (
  。。。
 ) {
  this.tag = tag
  this.data = data
  this.children = children
  this.text = text
  this.elm = elm
  this.ns = undefined
  this.context = context
  this.fnContext = undefined
  this.fnOptions = undefined
  this.fnScopeId = undefined
  this.key = data &amp;&amp; data.key
  this.componentOptions = componentOptions
  this.componentInstance = undefined
  this.parent = undefined
  this.raw = false
  this.isStatic = false
  this.isRootInsert = true
  this.isComment = false
  this.isCloned = false
  this.isOnce = false
  this.asyncFactory = asyncFactory
  this.asyncMeta = undefined
  this.isAsyncPlaceholder = false
 }
登录后复制

这个部分的代码 主要是为了更好地知道在diff算法中具体diff的属性的含义,当然也可以更好地了解vnode实例

整体过程

核心函数是patch函数

  • isUndef判断(是不是undefined或者null)

  • // empty mount (likely as component), create new root elementcreateElm(vnode, insertedVnodeQueue) 这里可以发现创建节点不是一个一个插入,而是放入一个队列中统一批处理

  • 核心函数sameVnode

function sameVnode (a, b) {
 return (
  a.key === b.key &amp;&amp; (
   (
    a.tag === b.tag &amp;&amp;
    a.isComment === b.isComment &amp;&amp;
    isDef(a.data) === isDef(b.data) &amp;&amp;
    sameInputType(a, b)
   ) || (
    isTrue(a.isAsyncPlaceholder) &amp;&amp;
    a.asyncFactory === b.asyncFactory &amp;&amp;
    isUndef(b.asyncFactory.error)
   )
  )
 )
}
登录后复制

这里是一个外层的比较函数,直接去比较了两个节点的key,tag(标签),data的比较(注意这里的data指的是VNodeData),input的话直接比较type。

export interface VNodeData {
 key?: string | number;
 slot?: string;
 scopedSlots?: { [key: string]: ScopedSlot };
 ref?: string;
 tag?: string;
 staticClass?: string;
 class?: any;
 staticStyle?: { [key: string]: any };
 style?: object[] | object;
 props?: { [key: string]: any };
 attrs?: { [key: string]: any };
 domProps?: { [key: string]: any };
 hook?: { [key: string]: Function };
 on?: { [key: string]: Function | Function[] };
 nativeOn?: { [key: string]: Function | Function[] };
 transition?: object;
 show?: boolean;
 inlineTemplate?: {
  render: Function;
  staticRenderFns: Function[];
 };
 directives?: VNodeDirective[];
 keepAlive?: boolean;
}
登录后复制

这会确认两个节点是否有进一步比较的价值,不然直接替换

替换的过程主要是一个createElm函数 另外则是销毁oldVNode

// destroy old node
    if (isDef(parentElm)) {
     removeVnodes(parentElm, [oldVnode], 0, 0)
    } else if (isDef(oldVnode.tag)) {
     invokeDestroyHook(oldVnode)
    }
登录后复制

插入过程简化来说就是判断node的type分别调用

createComponent(会判断是否有children然后递归调用)

createComment

createTextNode

创建后使用insert函数

之后需要用hydrate函数将虚拟dom和真是dom进行映射

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 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
  }
  if (isTrue(vnode.isStatic) &amp;&amp;
   isTrue(oldVnode.isStatic) &amp;&amp;
   vnode.key === oldVnode.key &amp;&amp;
   (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  ) {
   vnode.componentInstance = oldVnode.componentInstance
   return
  }
  let i
  const data = vnode.data
  if (isDef(data) &amp;&amp; isDef(i = data.hook) &amp;&amp; isDef(i = i.prepatch)) {
   i(oldVnode, vnode)
  }
  const oldCh = oldVnode.children
  const ch = vnode.children
  if (isDef(data) &amp;&amp; isPatchable(vnode)) {
   for (i = 0; i <blockquote><p>const el = vnode.el = oldVnode.el 这是很重要的一步,让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化。</p></blockquote><ol>
<li><p>比较二者引用是否一致</p></li>
<li><p>之后asyncFactory不知道是做什么的,所以这个比较看不懂</p></li>
<li><p>静态节点比较key,相同后也不做重新渲染,直接拷贝componentInstance(once命令在此生效)</p></li>
<li><p>如果vnode是文本节点或<a href="http://www.php.cn/code/8105.html" target="_blank">注释</a>节点,但是vnode.text != oldVnode.text时,只需要更新vnode.elm的文本内容就可以</p></li>
<li><p>children的比较</p></li>
</ol>
登录后复制
  • 如果只有oldVnode有子节点,那就把这些节点都删除

  • 如果只有vnode有子节点,那就创建这些子节点,这里如果oldVnode是个文本节点就把vnode.elm的文本设置为空字符串

  • 都有则updateChildren,这个之后详述

  • 如果oldVnode和vnode都没有子节点,但是oldVnode是文本节点或注释节点,就把vnode.elm的文本设置为空字符串

updateChildren

这部分重点还是关注整个算法

首先四个指针,oldStart,oldEnd,newStart,newEnd,两个数组,oldVnode,Vnode。

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
  while (oldStartIdx  oldEndIdx) {
   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  } else if (newStartIdx &gt; newEndIdx) {
   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
 }
登录后复制

一个循环比较的几种情况和处理(以下的++ --均指index的++ --)比较则是比较的node节点,简略写法 不严谨 比较用的是sameVnode函数也不是真的全等

整体循环不结束的条件oldStartIdx

  1. oldStart === newStart,oldStart++ newStart++

  2. oldEnd === newEnd,oldEnd-- newEnd--

  3. oldStart === newEnd, oldStart插到队伍末尾 oldStart++ newEnd--

  4. oldEnd === newStart, oldEnd插到队伍开头 oldEnd-- newStart++

  5. 剩下的所有情况都走这个处理简单的说也就两种处理,处理后newStart++

  • newStart在old中发现一样的那么将这个移动到oldStart前

  • 没有发现一样的那么创建一个放到oldStart之前

循环结束后并没有完成

还有一段判断才算完

if (oldStartIdx &gt; oldEndIdx) {
   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  } else if (newStartIdx &gt; newEndIdx) {
   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
登录后复制

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

axios发送post请求提交图片表单步骤详解

axios+post方法提交formdata步骤详解

以上就是diff算法使用技巧总结的详细内容,更多请关注php中文网其它相关文章!

diff算法使用详解

diff算法使用详解

这次给大家带来diff算法使用详解,diff算法使用的注意事项有哪些,下面就是实战案例,一起来看一下。

z虚拟dom

diff算法首先要明确一个概念就是diff的对象是虚拟dom,更新真实dom则是diff算法的结果

Vnode基类

 constructor (
  。。。
 ) {
  this.tag = tag
  this.data = data
  this.children = children
  this.text = text
  this.elm = elm
  this.ns = undefined
  this.context = context
  this.fnContext = undefined
  this.fnOptions = undefined
  this.fnScopeId = undefined
  this.key = data &amp;&amp; data.key
  this.componentOptions = componentOptions
  this.componentInstance = undefined
  this.parent = undefined
  this.raw = false
  this.isStatic = false
  this.isRootInsert = true
  this.isComment = false
  this.isCloned = false
  this.isOnce = false
  this.asyncFactory = asyncFactory
  this.asyncMeta = undefined
  this.isAsyncPlaceholder = false
 }
登录后复制

这个部分的代码 主要是为了更好地知道在diff算法中具体diff的属性的含义,当然也可以更好地了解vnode实例

整体过程

核心函数是patch函数

  • isUndef判断(是不是undefined或者null)

  • // empty mount (likely as component), create new root elementcreateElm(vnode, insertedVnodeQueue) 这里可以发现创建节点不是一个一个插入,而是放入一个队列中统一批处理

  • 核心函数sameVnode

function sameVnode (a, b) {
 return (
  a.key === b.key &amp;&amp; (
   (
    a.tag === b.tag &amp;&amp;
    a.isComment === b.isComment &amp;&amp;
    isDef(a.data) === isDef(b.data) &amp;&amp;
    sameInputType(a, b)
   ) || (
    isTrue(a.isAsyncPlaceholder) &amp;&amp;
    a.asyncFactory === b.asyncFactory &amp;&amp;
    isUndef(b.asyncFactory.error)
   )
  )
 )
}
登录后复制

这里是一个外层的比较函数,直接去比较了两个节点的key,tag(标签),data的比较(注意这里的data指的是VNodeData),input的话直接比较type。

export interface VNodeData {
 key?: string | number;
 slot?: string;
 scopedSlots?: { [key: string]: ScopedSlot };
 ref?: string;
 tag?: string;
 staticClass?: string;
 class?: any;
 staticStyle?: { [key: string]: any };
 style?: object[] | object;
 props?: { [key: string]: any };
 attrs?: { [key: string]: any };
 domProps?: { [key: string]: any };
 hook?: { [key: string]: Function };
 on?: { [key: string]: Function | Function[] };
 nativeOn?: { [key: string]: Function | Function[] };
 transition?: object;
 show?: boolean;
 inlineTemplate?: {
  render: Function;
  staticRenderFns: Function[];
 };
 directives?: VNodeDirective[];
 keepAlive?: boolean;
}
登录后复制

这会确认两个节点是否有进一步比较的价值,不然直接替换

替换的过程主要是一个createElm函数 另外则是销毁oldVNode

// destroy old node
    if (isDef(parentElm)) {
     removeVnodes(parentElm, [oldVnode], 0, 0)
    } else if (isDef(oldVnode.tag)) {
     invokeDestroyHook(oldVnode)
    }
登录后复制

插入过程简化来说就是判断node的type分别调用

createComponent(会判断是否有children然后递归调用)

createComment

createTextNode

创建后使用insert函数

之后需要用hydrate函数将虚拟dom和真是dom进行映射

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 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
  }
  if (isTrue(vnode.isStatic) &amp;&amp;
   isTrue(oldVnode.isStatic) &amp;&amp;
   vnode.key === oldVnode.key &amp;&amp;
   (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  ) {
   vnode.componentInstance = oldVnode.componentInstance
   return
  }
  let i
  const data = vnode.data
  if (isDef(data) &amp;&amp; isDef(i = data.hook) &amp;&amp; isDef(i = i.prepatch)) {
   i(oldVnode, vnode)
  }
  const oldCh = oldVnode.children
  const ch = vnode.children
  if (isDef(data) &amp;&amp; isPatchable(vnode)) {
   for (i = 0; i <blockquote><p>const el = vnode.el = oldVnode.el 这是很重要的一步,让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化。</p></blockquote><ol>
<li><p>比较二者引用是否一致</p></li>
<li><p>之后asyncFactory不知道是做什么的,所以这个比较看不懂</p></li>
<li><p>静态节点比较key,相同后也不做重新渲染,直接拷贝componentInstance(once命令在此生效)</p></li>
<li><p>如果vnode是文本节点或<a href="http://www.php.cn/code/8105.html" target="_blank">注释</a>节点,但是vnode.text != oldVnode.text时,只需要更新vnode.elm的文本内容就可以</p></li>
<li><p>children的比较</p></li>
</ol>
登录后复制
  • 如果只有oldVnode有子节点,那就把这些节点都删除

  • 如果只有vnode有子节点,那就创建这些子节点,这里如果oldVnode是个文本节点就把vnode.elm的文本设置为空字符串

  • 都有则updateChildren,这个之后详述

  • 如果oldVnode和vnode都没有子节点,但是oldVnode是文本节点或注释节点,就把vnode.elm的文本设置为空字符串

updateChildren

这部分重点还是关注整个算法

首先四个指针,oldStart,oldEnd,newStart,newEnd,两个数组,oldVnode,Vnode。

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
  while (oldStartIdx  oldEndIdx) {
   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  } else if (newStartIdx &gt; newEndIdx) {
   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
 }
登录后复制

一个循环比较的几种情况和处理(以下的++ --均指index的++ --)比较则是比较的node节点,简略写法 不严谨 比较用的是sameVnode函数也不是真的全等

整体循环不结束的条件oldStartIdx

  1. oldStart === newStart,oldStart++ newStart++

  2. oldEnd === newEnd,oldEnd-- newEnd--

  3. oldStart === newEnd, oldStart插到队伍末尾 oldStart++ newEnd--

  4. oldEnd === newStart, oldEnd插到队伍开头 oldEnd-- newStart++

  5. 剩下的所有情况都走这个处理简单的说也就两种处理,处理后newStart++

  • newStart在old中发现一样的那么将这个移动到oldStart前

  • 没有发现一样的那么创建一个放到oldStart之前

循环结束后并没有完成

还有一段判断才算完

if (oldStartIdx &gt; oldEndIdx) {
   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  } else if (newStartIdx &gt; newEndIdx) {
   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
登录后复制

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

JS EventEmitter使用详解

json数组键值大小写怎么转换(附代码)

以上就是diff算法使用详解的详细内容,更多请关注php中文网其它相关文章!

diff算法如何使用

diff算法如何使用

这次给大家带来diff算法如何使用,diff算法使用的注意事项有哪些,下面就是实战案例,一起来看一下。

虚拟dom

diff算法首先要明确一个概念就是diff的对象是虚拟dom,更新真实dom则是diff算法的结果

Vnode基类

 constructor (
  。。。
 ) {
  this.tag = tag
  this.data = data
  this.children = children
  this.text = text
  this.elm = elm
  this.ns = undefined
  this.context = context
  this.fnContext = undefined
  this.fnOptions = undefined
  this.fnScopeId = undefined
  this.key = data &amp;&amp; data.key
  this.componentOptions = componentOptions
  this.componentInstance = undefined
  this.parent = undefined
  this.raw = false
  this.isStatic = false
  this.isRootInsert = true
  this.isComment = false
  this.isCloned = false
  this.isOnce = false
  this.asyncFactory = asyncFactory
  this.asyncMeta = undefined
  this.isAsyncPlaceholder = false
 }
登录后复制

这个部分的代码 主要是为了更好地知道在diff算法中具体diff的属性的含义,当然也可以更好地了解vnode实例

整体过程

核心函数是patch函数

  • isUndef判断(是不是undefined或者null)

  • // empty mount (likely as component), create new root elementcreateElm(vnode, insertedVnodeQueue) 这里可以发现创建节点不是一个一个插入,而是放入一个队列中统一批处理

  • 核心函数sameVnode

function sameVnode (a, b) {
 return (
  a.key === b.key &amp;&amp; (
   (
    a.tag === b.tag &amp;&amp;
    a.isComment === b.isComment &amp;&amp;
    isDef(a.data) === isDef(b.data) &amp;&amp;
    sameInputType(a, b)
   ) || (
    isTrue(a.isAsyncPlaceholder) &amp;&amp;
    a.asyncFactory === b.asyncFactory &amp;&amp;
    isUndef(b.asyncFactory.error)
   )
  )
 )
}
登录后复制

这里是一个外层的比较函数,直接去比较了两个节点的key,tag(标签),data的比较(注意这里的data指的是VNodeData),input的话直接比较type。

export interface VNodeData {
 key?: string | number;
 slot?: string;
 scopedSlots?: { [key: string]: ScopedSlot };
 ref?: string;
 tag?: string;
 staticClass?: string;
 class?: any;
 staticStyle?: { [key: string]: any };
 style?: object[] | object;
 props?: { [key: string]: any };
 attrs?: { [key: string]: any };
 domProps?: { [key: string]: any };
 hook?: { [key: string]: Function };
 on?: { [key: string]: Function | Function[] };
 nativeOn?: { [key: string]: Function | Function[] };
 transition?: object;
 show?: boolean;
 inlineTemplate?: {
  render: Function;
  staticRenderFns: Function[];
 };
 directives?: VNodeDirective[];
 keepAlive?: boolean;
}
登录后复制

这会确认两个节点是否有进一步比较的价值,不然直接替换

替换的过程主要是一个createElm函数 另外则是销毁oldVNode

// destroy old node
    if (isDef(parentElm)) {
     removeVnodes(parentElm, [oldVnode], 0, 0)
    } else if (isDef(oldVnode.tag)) {
     invokeDestroyHook(oldVnode)
    }
登录后复制

插入过程简化来说就是判断node的type分别调用

createComponent(会判断是否有children然后递归调用)

createComment

createTextNode

创建后使用insert函数

之后需要用hydrate函数将虚拟dom和真是dom进行映射

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 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
  }
  if (isTrue(vnode.isStatic) &amp;&amp;
   isTrue(oldVnode.isStatic) &amp;&amp;
   vnode.key === oldVnode.key &amp;&amp;
   (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  ) {
   vnode.componentInstance = oldVnode.componentInstance
   return
  }
  let i
  const data = vnode.data
  if (isDef(data) &amp;&amp; isDef(i = data.hook) &amp;&amp; isDef(i = i.prepatch)) {
   i(oldVnode, vnode)
  }
  const oldCh = oldVnode.children
  const ch = vnode.children
  if (isDef(data) &amp;&amp; isPatchable(vnode)) {
   for (i = 0; i <blockquote><p>const el = vnode.el = oldVnode.el 这是很重要的一步,让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化。</p></blockquote><ol>
<li><p>比较二者引用是否一致</p></li>
<li><p>之后asyncFactory不知道是做什么的,所以这个比较看不懂</p></li>
<li><p>静态节点比较key,相同后也不做重新渲染,直接拷贝componentInstance(once命令在此生效)</p></li>
<li><p>如果vnode是文本节点或<a href="http://www.php.cn/code/8105.html" target="_blank">注释</a>节点,但是vnode.text != oldVnode.text时,只需要更新vnode.elm的文本内容就可以</p></li>
<li><p>children的比较</p></li>
</ol>
登录后复制
  • 如果只有oldVnode有子节点,那就把这些节点都删除

  • 如果只有vnode有子节点,那就创建这些子节点,这里如果oldVnode是个文本节点就把vnode.elm的文本设置为空字符串

  • 都有则updateChildren,这个之后详述

  • 如果oldVnode和vnode都没有子节点,但是oldVnode是文本节点或注释节点,就把vnode.elm的文本设置为空字符串

updateChildren

这部分重点还是关注整个算法

首先四个指针,oldStart,oldEnd,newStart,newEnd,两个数组,oldVnode,Vnode。

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
  while (oldStartIdx  oldEndIdx) {
   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  } else if (newStartIdx &gt; newEndIdx) {
   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
 }
登录后复制

一个循环比较的几种情况和处理(以下的++ --均指index的++ --)比较则是比较的node节点,简略写法 不严谨 比较用的是sameVnode函数也不是真的全等

整体循环不结束的条件oldStartIdx

  1. oldStart === newStart,oldStart++ newStart++

  2. oldEnd === newEnd,oldEnd-- newEnd--

  3. oldStart === newEnd, oldStart插到队伍末尾 oldStart++ newEnd--

  4. oldEnd === newStart, oldEnd插到队伍开头 oldEnd-- newStart++

  5. 剩下的所有情况都走这个处理简单的说也就两种处理,处理后newStart++

  • newStart在old中发现一样的那么将这个移动到oldStart前

  • 没有发现一样的那么创建一个放到oldStart之前

循环结束后并没有完成

还有一段判断才算完

if (oldStartIdx &gt; oldEndIdx) {
   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  } else if (newStartIdx &gt; newEndIdx) {
   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
登录后复制

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

json对象如何实现数组键值大小写转换功能

全局搜索时结果高亮如何实现

以上就是diff算法如何使用的详细内容,更多请关注php中文网其它相关文章!

我们今天的关于Myers diff算法与Hunt-McIlroy算法的分享已经告一段落,感谢您的关注,如果您想了解更多关于Diff算法、diff算法使用技巧总结、diff算法使用详解、diff算法如何使用的相关信息,请在本站查询。

本文标签: