Heim  >  Artikel  >  Web-Frontend  >  Eingehende Analyse des Prinzips des Diff-Algorithmus in vue2.x

Eingehende Analyse des Prinzips des Diff-Algorithmus in vue2.x

青灯夜游
青灯夜游nach vorne
2022-08-15 19:54:212049Durchsuche

Der Diff-Algorithmus ist ein effizienter Algorithmus, der Baumknoten auf derselben Ebene vergleicht und so die Notwendigkeit vermeidet, den Baum Schicht für Schicht zu durchsuchen und zu durchlaufen. Dieser Artikel bietet Ihnen eine ausführliche Analyse der Prinzipien des Diff-Algorithmus in vue2.x. Ich hoffe, er wird Ihnen hilfreich sein!

Eingehende Analyse des Prinzips des Diff-Algorithmus in vue2.x

Ich habe viele Artikel zur Quellcode-Analyse gelesen und den Quellcode mindestens zweimal gelesen. Schließlich möchte ich es immer noch selbst schreiben, als eine Art Aufzeichnung und zum Selbststudium. Konzentrieren Sie sich auf die Kommentare und die Zusammenfassung, machen Sie sich nicht zu viele Sorgen um den Rest. Sie werden mehr gewinnen, wenn Sie den Prozess und die Kommentare überprüfen, indem Sie die Zusammenfassung mit dem Quellcode vergleichen.

Aktualisieren Sie die Definition der Methode.

Nachdem die Renderfunktion generiert wurde, wird die Diff-Berechnung während der Initialisierung durchgeführt, da kein alter virtueller Knoten vorhanden ist Da es sich bei dem virtuellen Knoten nicht um einen nativen Knoten handelt, wird zum ersten Mal ein neuer Ersatzvorgang durchgeführt. (Teilen von Lernvideos: vue-Video-Tutorial)

// /src/core/instance/lifecycle.js
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode // 当前render函数产生的虚拟节点,保存后以便下次做对比
    if (!prevVnode) {
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false) //初次渲染
    } else {
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
   ...
  }

diff-Algorithmus hat zwei Hauptzweige

Der Hauptteil besteht aus zwei Hauptzweigen: Die vorderen und hinteren virtuellen Knoten sind konsistent, und die vorderen und hinteren virtuellen Knoten sind konsistent inkonsistent

// /src/core/vdom/patch.js
export function createPatchFunction (backend) {
  ...
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    ...
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        ...// 前后虚拟节点一致的方法
      } else {
        ...// 前后虚拟节点不一致的方法
      }
  }
}

Die vorderen und hinteren virtuellen Knoten sind inkonsistent

Es ist in drei Schritte unterteilt: 1. Erstellen Sie einen neuen Knoten, 2. Aktualisieren Sie den übergeordneten Platzhalterknoten, 3. Löschen Sie den alten Knoten
Die beiden sind unterschiedlich Die Komponente wird zum ersten Mal gemountet. Anschließend wird beurteilt, ob es sich um einen echten Dom handelt. Er wird in einen virtuellen Knoten umgewandelt und ersetzt. Die virtuellen Knoten davor und danach sind konsistent. Bestimmen Sie zuerst Wenn ja, legen Sie den Text direkt fest. Wenn nicht, fahren Sie mit der Bestimmung fort

Der neue und der alte Knoten haben Kinder. Detaillierter Vergleich (Schlüsselpunkt)Der neue Knoten hat Kinder, der alte Knoten nicht, neue Knoten in einer Schleife hinzufügen

    Der neue Knoten hat keine Kinder, der alte Knoten hat Kinder, löschen Sie den alten Knoten direkt
if (isRealElement) {
  ...
  //需要diff 所以将第一次的真实节点转换成虚拟节点
  oldVnode = emptyNodeAt(oldVnode) //<div id="app"></div>
}
// 拿到父类的dom节点
const oldElm = oldVnode.elm //app
const parentElm = nodeOps.parentNode(oldElm) // body
//创建新dom节点 内部包含组件逻辑
createElm(
  vnode,
  insertedVnodeQueue,
  oldElm._leaveCb ? null : parentElm,
  nodeOps.nextSibling(oldElm)
)
//更新父的占位符节点 (组件更新相关)
if (isDef(vnode.parent)) {
  // 在生成render函数时会生成占位符节点<Dialog>提示</Dialog> => <div>提示</div> <Dialog></Dialog>就是占位符节点
  let ancestor = vnode.parent
  // 判断是否可挂载
  const patchable = isPatchable(vnode)
  while (ancestor) {
    for (let i = 0; i < cbs.destroy.length; ++i) {
      cbs.destroy[i](ancestor)
    }
    //更新父占位符的element
    ancestor.elm = vnode.elm
    if (patchable) {
      ...
    } else {
      registerRef(ancestor)
    }
    ancestor = ancestor.parent
  }
}
// 删除旧节点
if (isDef(parentElm)) {
  removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
  invokeDestroyHook(oldVnode)
}
  • Vergleich zwischen alten und neuen Knoten mit Kindern
  • function patchVnode (oldVnode,vnode,insertedVnodeQueue,ownerArray,index,removeOnly) {
        const elm = vnode.elm = oldVnode.elm
    
        let i
        const data = vnode.data 
        // 是组件vnode,在组件更新会调用组件的prepatch方法
        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)
        }
        // 是否是text
        if (isUndef(vnode.text)) {
          // 新旧节点都有children
          if (isDef(oldCh) && isDef(ch)) {
            if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
          // 新有 老没有 children 循环创建新节点
          } else if (isDef(ch)) {
            if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
            addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
          // 新没有 老有 children 直接删除老节点
          } else if (isDef(oldCh)) {
            removeVnodes(oldCh, 0, oldCh.length - 1)
          // 新老都没有 children 老的是文本 就置为空
          } else if (isDef(oldVnode.text)) {
            nodeOps.setTextContent(elm, '')
          }
        // 是text 直接设置文本
        } 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)
        }
      }
  • Hinweis
  • In einigen Fällen kann der Index nicht als Schlüssel verwendet werden, da der Index vor und nach der Änderung derselbe ist. Wenn der Index als Schlüssel verwendet wird, tritt ein Aktualisierungsfehler auf Ich werde es so verstehen, dass am Ende ein Element hinzugefügt wird (da die Schlüssel davor und danach alle 0 sind). Vergleiche, die Rolle des Schlüssels ist groß. Ohne Schlüssel treten Aktualisierungsfehler auf und der Wiederverwendungseffekt wird nicht erreicht.

    Tiefe zuerst, Vergleich derselben Ebene

      Zusammenfassung
    • Die Vorlage wird beim Mounten nach dem Diff-Algorithmus aktualisiert. Zum ersten Mal wird der reale DOM-Knoten mit dem generierten virtuellen Knoten verglichen und der generierte virtuelle Knoten wird zur späteren Aktualisierung und zum späteren Vergleich gespeichert. Der Diff-Algorithmus muss nur in zwei Zweige unterteilt werden: Die vorderen und hinteren virtuellen Knoten sind konsistent und die vorderen und hinteren virtuellen Knoten sind inkonsistent. Wenn die vorderen und hinteren virtuellen Knoten inkonsistent sind, wird ein neuer Knoten erstellt, der übergeordnete Platzhalter aktualisiert und der alte Knoten gelöscht. Wenn der alte Knoten ein realer Knoten ist, konvertieren Sie ihn in einen virtuellen Knoten, rufen Sie den übergeordneten Knoten des alten Knotens ab und ersetzen Sie den alten Knoten. Wenn die vorderen und hinteren virtuellen Knoten konsistent sind, wird zunächst festgestellt, ob der neue Knoten Text ist. Wenn der Wert direkt hinzugefügt wird, vergleichen Sie, wenn nicht, zuerst die Attribute und bestimmen Sie dann, ob der neue Knoten Kinder hat und der alte Knoten keine Wenn der neue Knoten untergeordnete Elemente enthält, werden die untergeordneten Elemente des alten Knotens direkt hinzugefügt Neue Knoten haben untergeordnete Knoten. Doppelzeiger werden verwendet, um dieselbe Ebene durch Kopf-an-Kopf-Vergleich, Schwanz-an-Schwanz-Vergleich, Kopf-an-Schwanz-Vergleich, Schwanz-an-Kopf-Vergleich und Out-of-Head-Vergleich zu vergleichen. Beurteilen und aktualisieren Sie sie kontinuierlich, um die Nutzung alter Knoten zu maximieren. Wenn redundante neue Knoten vorhanden sind, werden diese gelöscht. Schließlich werden die verglichenen virtuellen Knoten zurückgegeben Nächstes Mal Alter Knoten zum Vergleichen.
      • Kopf-an-Kopf-Vergleich
        Wenn sich die neuen und alten Startelemente im selben Vnode befinden, rufen Sie rekursiv die Methode patchVnode für einen umfassenden Vergleich auf und verschieben Sie dann den Index zum nächsten ElementpatchVnode方法进行深层对比,之后移动索引至下一个元素
      • 尾尾对比
        如果新旧结束元素是相同vnode,递归调用patchVnode方法进行深层对比,之后移动索引至上一个元素
      • 头尾对比
        将老节点开始元素和旧节点尾元素进行对比,相同就递归调用patchVnode方法进行深层对比,之后将旧节点元素移动至最后,旧节点头指针移动到下一个,新节点的尾指针移动至上一个。例如旧:A,B,C,新:C,B,A,第一次对比将旧A移动到C后边
      • 尾头对比
        将老节点尾元素和旧节点开始元素进行对比,相同就递归调用patchVnode
      • Tail -to-tail-Vergleich
      • Wenn sich die alten und neuen Endelemente im selben Vnode befinden, rufen Sie rekursiv die Methode patchVnode für einen tiefen Vergleich auf und verschieben Sie dann den Index zum vorherigen Element
      • Head-to- Schwanzvergleich
      Vergleichen Sie das Startelement des alten Knotens mit dem Schwanzelement des alten Knotens. Wenn dasselbe, rufen Sie patchVnoderekursiv auf. > Die Methode führt einen tiefen Vergleich durch und verschiebt dann das alte Knotenelement an das Ende den Kopfzeiger des alten Knotens auf den nächsten und verschiebt den Schwanzzeiger des neuen Knotens auf den vorherigen. Zum Beispiel alt: A, B, C, neu: C, B, A. Verschieben Sie für den ersten Vergleich das alte A an die Rückseite von C

      Schwanz- und KopfvergleichVergleichen Sie das Schwanzelement des alten Knotens mit dem Wenn sie gleich sind, rufen Sie die Methode patchVnode für einen tiefen Vergleich auf und verschieben Sie dann das alte Knotenelement nach vorne, wobei der Endzeiger des alten Knotens auf den vorherigen verweist. und der Kopfzeiger des neuen Knotens auf den nächsten. Zum Beispiel alt: A, B, C, neu: C, B, A, D. Beim ersten Vergleich wird das alte C an den Anfang von A verschoben.Vergleich außerhalb der Reihenfolge

      Der alte Knoten wird basierend auf generiert auf den Schlüssel und den entsprechenden Index vor dem Vergleich. Beim Out-of-Order-Vergleich wird der aktuelle Schlüssel verwendet, um den Schlüssel des alten Knotens zu finden. Wenn er wiederverwendet werden kann, wird der Knoten an den Anfang des alten Knotens verschoben und die untergeordneten Knoten werden rekursiv verglichen Es kann nicht wiederverwendet werden, es wird ein neues erstellt und am Anfang des Knotens in das alte eingefügt. Verschieben Sie dann den neuen Index zum nächsten Element🎜🎜🎜 (Lernvideo-Sharing: 🎜Web-Front-End-Entwicklung🎜, 🎜Einfaches Programmiervideo🎜)🎜

    Das obige ist der detaillierte Inhalt vonEingehende Analyse des Prinzips des Diff-Algorithmus in vue2.x. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

  • Stellungnahme:
    Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen