Ein kurzer Vortrag über den Diff-Algorithmus in Vue

Ein kurzer Vortrag über den Diff-Algorithmus in Vue

Überblick

Man kann sagen, dass der Diff-Algorithmus ein Kerninhalt von Vue ist. Ich habe Vue vorher nur für einige Entwicklungsarbeiten verwendet und wusste nicht viel über den spezifischen Kerninhalt. Vor kurzem habe ich zufällig einige Inhalte in diesem Bereich gelesen. Lassen Sie uns über die Implementierung des Diff-Algorithmus von Vue2.0 sprechen. Insbesondere werden wir ihn anhand mehrerer implementierter Funktionen analysieren.

Virtueller Dom

Virtueller DOM extrahiert die Daten des realen DOM und simuliert die Baumstruktur in Form von Objekten.

Zum Beispiel ist das Folgende unser realer DOM

<div>
   <p>1234</p>
   <div>
       <span>1111</span>
   </div>
</div>

Der entsprechend dem realen DOM generierte virtuelle DOM sieht wie folgt aus

 var Vnode = {
     Tag: 'div',
     Kinder: [
         {
             Tag: 'p',
             Text: '1234'
         },
         {
             Tag: 'div',
             Kinder:[
                 {
                     Tag: 'span',
                     Text: '1111'
                 }
             ]
         }
     ]
 }

Prinzip

Das Prinzip von Diff besteht darin, dass der aktuelle reale DOM einen virtuellen DOM erzeugt. Wenn sich die Daten eines Knotens im virtuellen DOM ändern, wird ein neuer Vnode erzeugt. Anschließend wird dieser Vnode mit dem alten oldVnode verglichen. Wenn ein Unterschied besteht, wird dieser direkt im realen DOM geändert.

Implementierungsprozess

Der Kern des Implementierungsprozesses des Diff-Algorithmus ist Patch. Die Methoden patchVnode, sameVnode und updateChildren verdienen unsere Aufmerksamkeit. Im Folgenden werden sie der Reihe nach erläutert.

Patch-Methode

Die Kernlogik von Patch besteht darin, zwei Vnode-Knoten zu vergleichen und dann die Unterschiede in der Ansicht zu aktualisieren. Die Vergleichsmethode ist ein Peer-Vergleich, anstatt jede Ebene zu durchlaufen. Wenn nach dem Vergleich Unterschiede gefunden werden, werden diese Unterschiede in der Ansicht aktualisiert. Das Beispiel für die Vergleichsmethode lautet wie folgt

sameVnode-Funktion

Die Funktion von sameVnode besteht darin, zu bestimmen, ob zwei Knoten gleich sind. Grundlage für die Bestimmung sind Schlüsselwert, Tag, isCommit, ob der Eingabetyp konsistent ist usw. Diese Methode weist einige Mängel auf. In Situationen, in denen der Schlüsselwert unter v-for einen Index verwendet, kann er auch als wiederverwendbarer Knoten beurteilt werden.

Es wird empfohlen, den Index nicht als Schlüsselwert zu verwenden.

patchVnode-Funktion

// Mehrere Parameter übergeben, oldVnode stellt den alten Knoten dar, vnode stellt den neuen Knoten dar, readOnly stellt dar, ob es sich um eine schreibgeschützte Knotenfunktion handelt patchVnode (
    alterVnode,
    vKnoten,
    eingefügteVnodeQueue,
    BesitzerArray,
    Index,
    Nur entfernen
  ) {
    if (oldVnode === vnode) { //Wenn der alte Knoten und der neue Knoten konsistent sind, ist kein Vergleich erforderlich, return return
    }

    wenn (isDef(vnode.elm) und isDef(ownerArray)) {   
      // wiederverwendeten vnode klonen
      vnode = BesitzerArray[Index] = Klon-VNode(vnode)
    }

    const elm = vnode.elm = alterVnode.elm

    wenn (istTrue(oldVnode.isAsyncPlaceholder)) {
      wenn (isDef(vnode.asyncFactory.gelöst)) {
        hydratisieren(alterVnode.elm, vnode, eingefügteVnodeQueue)
      } anders {
        vnode.isAsyncPlaceholder = true
      }
      zurückkehren
    }

    //Elemente des statischen Baums wiederverwenden //Das machen wir nur, wenn der Vnode geklont ist. //Wenn der neue Knoten nicht geklont ist, bedeutet das, dass die Rendering-Funktion geklont wurde. //Zurückgesetzt über Hot-Reload-API, und wir müssen ein richtiges erneutes Rendering durchführen.
    wenn (isTrue(vnode.isStatic) &&
      isTrue(alterVnode.isStatic) &&
      vnode.key === alterVnode.key &&
      (istWahr(vnode.isCloned) || istWahr(vnode.isOnce))
    ) {
      vnode.componentInstance = alteVnode.componentInstance
      zurückkehren
    }

    lass mich
    const data = vnode.data
    wenn (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
      i(alterV-Knoten, V-Knoten)
    }

    const oldCh = oldVnode.children
    const ch = vnode.children
    wenn (isDef(data) und isPatchable(vnode)) {
      für (i = 0; i < cbs.update.length; ++i) cbs.update[i](alterVnode, vnode)
      wenn (isDef(i = data.hook) und isDef(i = i.update)) i(alterVnode, vnode)
    }
    wenn (isUndef(vnode.text)) {
      wenn (isDef(alterCh) und isDef(ch)) {
        wenn (alterKanal !== ch) updateChildren(elm, alterKanal, ch, eingefügteVnodeQueue, nurentfernen)
      } sonst wenn (isDef(ch)) {
        wenn (Prozess.Umgebung.NODE_ENV !== 'Produktion') {
          checkDuplicateKeys(ch)
        }
        wenn (isDef(alterVnode.text)) nodeOps.setTextContent(elm, '')
        addVnodes(elm, null, ch, 0, ch.length - 1, eingefügteVnodeQueue)
      } sonst wenn (isDef(oldCh)) {
        removeVnodes(alterKanal, 0, alterKanal.Länge - 1)
      } sonst wenn (isDef(alterVnode.text)) {
        nodeOps.setTextContent(ulme, '')
      }
    } sonst wenn (alterVnode.text !== vnode.text) {
      nodeOps.setTextContent(elm, vnode.text)
    }
    wenn (isDef(Daten)) {
      wenn (isDef(i = data.hook) und isDef(i = i.postpatch)) i(alterVnode, vnode)
    }
  }

Die spezifische Implementierungslogik ist:

  1. Wenn der alte und der neue Knoten identisch sind, sind keine Änderungen erforderlich und der Knoten wird direkt zurückgegeben.
  2. Wenn sowohl der alte als auch der neue Knoten statische Knoten sind und denselben Schlüssel haben, kopieren Sie einfach oldVnode.elm und oldVnode.child nach vnode, wenn vnode ein geklonter Knoten oder ein von der Direktive v-once gesteuerter Knoten ist.
  3. Bestimmen Sie, ob der Vnode ein Kommentarknoten oder ein Textknoten ist, und führen Sie dann die folgende Verarbeitung durch
    1. Wenn vnode ein Textknoten oder Kommentarknoten ist und vnode.text!== oldVnode.text ist, muss nur der Textinhalt von vnode aktualisiert werden;
    2. Sowohl oldVnode als auch vndoe haben untergeordnete Knoten. Wenn die untergeordneten Knoten unterschiedlich sind, wird die Methode updateChildren aufgerufen. Die spezifische Implementierung ist wie folgt
    3. Wenn nur vnode untergeordnete Knoten hat, bestimmen Sie die Umgebung. Wenn es sich nicht um eine Produktionsumgebung handelt, rufen Sie die Methode checkDuplicateKeys auf, um zu bestimmen, ob der Schlüsselwert wiederholt wird. Fügen Sie dann den aktuellen ch zu oldVnode hinzu
    4. Wenn nur oldVnode untergeordnete Knoten hat, rufen Sie die Methode zum Löschen des aktuellen Knotens auf

updateChildren-Funktion

updateChildren ist, wie der Name schon sagt, eine Methode zum Aktualisieren von untergeordneten Knoten. Aus der obigen patchVnode-Methode können wir ersehen, dass diese Methode ausgeführt wird, wenn sowohl der neue als auch der alte Knoten untergeordnete Knoten haben. Werfen wir einen Blick auf die Implementierungslogik. Es gibt auch einige Beispieldiagramme, die Ihnen vielleicht schon einmal ähnlich erschienen sind. Werfen wir zunächst einen Blick auf den Code.

Funktion updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = alteCh.Länge - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    lass newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm

    // removeOnly ist ein spezielles Flag, das nur von <transition-group> verwendet wird
    // um sicherzustellen, dass entfernte Elemente an den richtigen relativen Positionen bleiben
    // während des Verlassens von Übergängen
    const canMove = !removeOnly

    wenn (Prozess.Umgebung.NODE_ENV !== 'Produktion') {
      checkDuplicateKeys(neuerSchlüssel)
    }

    während (alteStartIdx <= alteEndIdx && neueStartIdx <= neueEndIdx) {
      wenn (isUndef(alterStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // Vnode wurde nach links verschoben
      } sonst wenn (isUndef(oldEndVnode)) {
        oldEndVnode = alterCh[--oldEndIdx]
      } sonst wenn (gleicherVnode(alterStartVnode, neuerStartVnode)) {
        patchVnode(alterStartVnode, neuerStartVnode, eingefügteVnodeQueue, neuerCh, neueStartIdx)
        alterStartVnode = alterCh[++alteStartIdx]
        neuerStartVnode = neuerCh[++neueStartIdx]
      } sonst wenn (gleicherVnode(alterVnodeEndknoten, neuerVnodeEndknoten)) {
        patchVnode(alterEndVnode, neuerEndVnode, eingefügteVnodeQueue, neuerCh, neueEndIdx)
        oldEndVnode = alterCh[--oldEndIdx]
        neuerEndVnode = neuerCh[--newEndIdx]
      } sonst wenn (sameVnode(oldStartVnode, newEndVnode)) { // Vnode nach rechts verschoben
        patchVnode(alterStartVnode, neuerEndVnode, eingefügteVnodeQueue, neuerCh, neueEndIdx)
        kann verschieben && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        alterStartVnode = alterCh[++alteStartIdx]
        neuerEndVnode = neuerCh[--newEndIdx]
      } sonst wenn (sameVnode(oldEndVnode, newStartVnode)) { // Vnode nach links verschoben
        patchVnode(alterEnd-Vnode, neuerStart-Vnode, eingefügteVnodeQueue, neuerCh, neueStartIdx)
        kann verschieben und nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = alterCh[--oldEndIdx]
        neuerStartVnode = neuerCh[++neueStartIdx]
      } anders {
        wenn (isUndef(alterKeyToIdx)) alterKeyToIdx = createKeyToOldIdx(alterCh, alteStartIdx, alteEndIdx)
        idxInOld = isDef(neuerStartVnode.key)
          ? oldKeyToIdx[neuerStartVnode.key]
          : findIdxInOld(neuerStartVnode, alterCh, alteStartIdx, alteEndIdx)
        if (isUndef(idxInOld)) { // Neues Element
          createElm(neuerStartVnode, eingefügteVnodeQueue, übergeordneteElm, alterStartVnode.elm, false, neuerCh, neueStartIdx)
        } anders {
          vnodeToMove = alterCh[idxInOld]
          wenn (sameVnode(vnodeToMove, newStartVnode)) {
            patchVnode(ZuVerschiebender vnode, neuerStartVnode, eingefügteVnodeQueue, neuerCh, neueStartIdx)
            oldCh[idxInOld] = undefiniert
            kann verschieben und nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } anders {
            // gleicher Schlüssel, aber anderes Element. Als neues Element behandeln
            createElm(neuerStartVnode, eingefügteVnodeQueue, übergeordneteElm, alterStartVnode.elm, false, neuerCh, neueStartIdx)
          }
        }
        neuerStartVnode = neuerCh[++neueStartIdx]
      }
    }
    wenn (alteStartIdx > alteEndIdx) {
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, eingefügteVnodeQueue)
    } sonst wenn (neueStartIdx > neueEndIdx) {
      removeVnodes(alterCh, alteStartIdx, alteEndIdx)
    }
  }

Hier definieren wir zunächst mehrere Parameter: oldStartIdx (erster Index des alten Knotens), oldEndIdx (letzter Index des alten Knotens), oldStartVnode (erstes Element des alten Knotens), oldEndVnode (letztes Element des alten Knotens); analog dazu sind newStartIdx und die anderen vier Elemente der erste Index des neuen Knotens usw.

Schauen Sie sich die Operationen in der while-Schleife an, die auch den Kerninhalt darstellen

Nachdem festgestellt wurde, dass es sich um denselben Knoten handelt, muss der Knoten auch mit der Methode patchVnode fortfahren

  • Wenn das alte erste Element und das neue erste Element derselbe Knoten sind, werden der alte erste Index und der neue erste Index gleichzeitig nach rechts verschoben.
  • Wenn das alte und das neue Endelement derselbe Knoten sind, werden der alte und der neue Endindex gleichzeitig nach links verschoben.
  • Wenn sich das alte erste Element und das neue letzte Element am selben Knoten befinden, wird gemäß der von der Methode hochgeladenen schreibgeschützten Beurteilung, wenn diese falsch ist, das alte erste Element zum letzten Index des alten Knotens verschoben und gleichzeitig der alte erste Index nach rechts und der neue letzte Index nach links verschoben.
  • Wenn sich das alte Endelement und das neue erste Element am selben Knoten befinden, wird gemäß der von der Methode hochgeladenen schreibgeschützten Beurteilung, wenn diese falsch ist, das alte Endelement an die vorherige Position des alten Knotens verschoben und gleichzeitig der alte Endindex nach links und der neue erste Index nach rechts verschoben.
  • Wenn keine der oben genannten Bedingungen erfüllt ist, ermitteln Sie, ob in oldCh ein Vnode mit demselben Schlüssel wie newStartVnode vorhanden ist. Wenn er nicht gefunden wird, handelt es sich um einen neuen Knoten. Erstellen Sie einen neuen Knoten und fügen Sie ihn ein.

Wenn ein Vnode mit demselben Schlüssel wie newStartVnode gefunden wird, wird er vnodeToMove genannt und dann wird vnodeToMove mit newStartVnode verglichen. Wenn sie gleich sind, werden beide erneut gepatcht. Wenn removeOnly falsch ist, wird der Vnode mit demselben Schlüssel wie newStartVnode, genannt vnodeToMove.elm, vor oldStartVnode.elm verschoben. Wenn der Schlüsselwert gleich ist, die Knoten jedoch unterschiedlich sind, wird ein neuer Knoten erstellt.

Wenn nach der While-Schleife noch Knoten im neuen oder alten Knoten-Array vorhanden sind, löschen oder fügen Sie diese je nach spezifischer Situation hinzu.

Wenn oldStartIdx > oldEndIdx ist, bedeutet dies, dass oldCh zuerst durchlaufen wird, was bedeutet, dass es neue Knoten gibt, die redundant sind. Neue Knoten hinzufügen

Wenn newStartIdx > newEndIdx, bedeutet dies, dass der neue Knoten zuerst durchlaufen wird und noch alte Knoten übrig sind. Löschen Sie daher die verbleibenden Knoten.

Schauen wir uns das Beispielbild unten an

Ursprünglicher Knoten (oldVnode ist der alte Knoten, Vnode ist der neue Knoten und diff ist das nach dem Diff-Algorithmus generierte Knotenarray)

Beim ersten Durchlaufen stellen wir fest, dass das alte Endelement mit dem neuen ersten Element identisch ist. Daher wird das alte Endelement D vor den alten ersten Index verschoben, also vor A. Gleichzeitig wird der alte Endindex nach links und der neue erste Index nach rechts verschoben.

Beim zweiten Durchlauf der Schleife ist das neue erste Element dasselbe wie das alte erste Element. Zu diesem Zeitpunkt ändern sich die Positionen der beiden Elemente nicht, und die neuen und alten ersten Indizes verschieben sich gleichzeitig nach rechts.

Die dritte Schleife stellt fest, dass es im alten Element keinen Knoten gibt, der mit dem aktuellen Element identisch ist. Daher wird ein neuer hinzugefügt und F wird vor das alte erste Element gesetzt. In ähnlicher Weise verhält es sich mit der vierten Schleife. Nach zwei Schleifen wird ein neues Beispieldiagramm generiert.

Der fünfte Zyklus ist derselbe wie der zweite Zyklus.

In der sechsten Schleife bewegt sich newStartIdx wieder nach rechts

7. Nach dem letzten Zug ist newStartIdx > newEndIdx und die while-Schleife wurde verlassen, was beweist, dass newCh zuerst durchlaufen wird und oldCh noch zusätzliche Knoten hat, die direkt gelöscht werden, sodass der letzte Knoten

Oben sind mehrere Funktionen im Zusammenhang mit dem Diff-Algorithmus und dem Implementierungsprozess des Diff-Algorithmus aufgeführt.

Abschluss

Der Diff-Algorithmus ist ein zentraler Bestandteil des virtuellen DOM. Er vergleicht dieselbe Ebene und aktualisiert die geänderten Teile mit dem realen DOM, indem er die neuen und alten Knoten vergleicht.

Die spezifischen Implementierungsmethoden sind patch, patchVnode und updateChildren

Der Kern des Patches besteht darin, dass, wenn der neue Knoten existiert, der alte Knoten jedoch nicht, dieser hinzugefügt wird; wenn der alte Knoten existiert, der neue jedoch nicht, dieser gelöscht wird; wenn beide existieren, festgestellt wird, ob sie identisch sind; wenn sie identisch sind, patchVnode für den nächsten Vergleichsschritt aufgerufen wird.

Der Kern von patchVnode lautet: Wenn die neuen und alten Knoten keine Kommentare oder Textknoten sind und der neue Knoten untergeordnete Knoten hat, der alte jedoch nicht, fügen Sie einen untergeordneten Knoten hinzu. Wenn der neue Knoten keine untergeordneten Knoten hat, der alte Knoten jedoch untergeordnete Knoten hat, löschen Sie die untergeordneten Knoten unter dem alten Knoten. Wenn beide untergeordnete Knoten haben, rufen Sie die Methode updateChildren auf.
Der Kern von updateChildren besteht darin, die neuen und alten Knoten zu vergleichen und sie hinzuzufügen, zu löschen oder zu aktualisieren.

Dies ist nur eine vorläufige Erklärung des Diff-Algorithmus von Vue2.0. Die tieferen Prinzipien und ob es Änderungen im Diff-Algorithmus von Vue3.0 gibt, müssen noch herausgefunden werden.

Dies ist das Ende dieses Artikels über den Diff-Algorithmus in Vue. Weitere Informationen zum Diff-Algorithmus von Vue finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, Sie werden 123WORDPRESS.COM auch in Zukunft unterstützen!

Das könnte Sie auch interessieren:
  • Kennen Sie den virtuellen DOM- und Diff-Algorithmus von Vue?
  • Vollständige Analyse des Vue-Diff-Algorithmus
  • Detaillierte Erklärung des Diff-Algorithmus von Vue2
  • Detaillierte Erklärung zur Verwendung des Vue3.0-Diff-Algorithmus (sehr detailliert)
  • Detaillierte Erklärung des Diff-Algorithmusprinzips von Vue
  • Verstehen Sie das Prinzip des Diff-Algorithmus von Vue wirklich?

<<:  Grafisches Tutorial zur Installation und Konfiguration von MySQL 8.0.20

>>:  Installations- und Konfigurationstutorial von MongoDB unter Linux

Artikel empfehlen

Detailliertes Beispiel zum Entfernen doppelter Daten in MySQL

Detailliertes Beispiel zum Entfernen doppelter Da...

So schreiben Sie den Stil einer CSS3-Tianzi-Rasterliste

In vielen Projekten ist es notwendig, die Funktio...

MySql-Freigabe der Nullfunktionsnutzung

Funktionen zu Null in MySql IFNULL ISNULL NULLIF ...

Tipps zum MySQL-Abfragecache

Inhaltsverzeichnis Vorwort Einführung in QueryCac...

Web 2.0: Ursachen und Lösungen der Informationsüberflutung

<br />Informationsduplikation, Informationsü...

Detaillierte Erklärung der Rolle des neuen Operators in Js

Vorwort Js ist heutzutage die am häufigsten verwe...

Analyse von drei Parametern des MySQL-Replikationsproblems

Inhaltsverzeichnis 01 sql_slave_skip_counter-Para...

CSS3-Implementierungscode für einfaches Karussellbildschneiden

Umsetzungsideen Erstellen Sie zunächst einen über...

Methode der Iframe-Anpassung im webresponsiven Layout

Problem <br />Bei responsivem Layout sollte...

Lassen Sie uns über den Unterschied zwischen MyISAM und InnoDB sprechen

Die Hauptunterschiede sind folgende: 1. MySQL ver...