Vollständige Analyse des Vue-Diff-Algorithmus

Vollständige Analyse des Vue-Diff-Algorithmus

Vorwort

Wir wissen, dass Vue virtuelles DOM verwendet, um die Anzahl der Operationen auf dem realen DOM zu reduzieren und so die Effizienz der Seitenoperationen zu verbessern. Heute schauen wir uns an, wie Vue das DOM aktualisiert, wenn sich die Seitendaten ändern. Vue und React verwenden beim Aktualisieren von DOM grundsätzlich denselben Algorithmus, beide basieren auf Snabbdom. Wenn sich die Daten auf der Seite ändern, erfolgt das Rendering durch Vue nicht sofort. Stattdessen wird der Diff-Algorithmus verwendet, um zu bestimmen, welche Teile nicht geändert werden müssen und welche Teile geändert und aktualisiert werden müssen. Nur die DOM-Teile, die aktualisiert werden müssen, müssen aktualisiert werden. Dies reduziert viele unnötige DOM-Operationen und verbessert die Leistung erheblich. Vue verwendet einen solchen abstrakten Knoten VNode, der eine Abstraktion des echten DOM darstellt und nicht von einer bestimmten Plattform abhängig ist. Es kann eine Browserplattform, Weex oder sogar eine Knotenplattform sein. Es kann auch einen solchen abstrakten DOM-Baum erstellen, löschen und ändern, was auch Front-End- und Back-End-Isomorphismus ermöglicht.

Vue Update-Ansicht

Wir wissen, dass in Vue 1.x alle Daten einem Watcher entsprechen; in Vue 2.x entspricht eine Komponente einem Watcher. Wenn sich unsere Daten ändern, löst die Set-Funktion daher die Benachrichtigungsfunktion des Dep aus, um den Watcher zu benachrichtigen, die Methode vm._update(vm._render(), hydrating) auszuführen, um die Ansicht zu aktualisieren. Werfen wir einen Blick auf die Methode _update.

Vue.prototype._update = Funktion (vnode: VNode, hydratisieren?: Boolesch) {
  const vm:Komponente = diese
  const prevEl = vm.$el
  const prevVnode = vm._vnode
  const restoreActiveInstance = setActiveInstance(vm)
  vm._vnode = vnode
  // Vue.prototype.__patch__ wird in Einstiegspunkte eingefügt
  // basierend auf dem verwendeten Rendering-Backend.
  /*Basierend auf dem Backend-Rendering wird Vue.prototype.__patch__ als Einstiegspunkt verwendet*/
  wenn (!prevVnode) {
    // erstes Rendern
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /*Nur entfernen */)
  } anders {
    // Aktualisierungen
    vm.$el = vm.__patch__(vorherigerVnode, vnode)
  }
  restoreActiveInstance()
  // __vue__-Referenz aktualisieren
  /*Aktualisiere das __vue__ des neuen Instanzobjekts*/
  if (vorheriges) {
    vorherigeEl.__vue__ = null
  }
  wenn (vm.$el) {
    vm.$el.__vue__ = vm
  }
  // wenn das übergeordnete Element ein HOC ist, aktualisiere auch dessen $el
  wenn (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
    vm.$parent.$el = vm.$el
  }
  // Der aktualisierte Hook wird vom Scheduler aufgerufen, um sicherzustellen, dass die untergeordneten Elemente
  // aktualisiert im aktualisierten Hook eines übergeordneten Elements.
}

Offensichtlich können wir sehen, dass die _update-Methode den alten Vnode mit dem übergebenen Vnode patcht. Schauen wir uns als Nächstes an, was in der Patch-Funktion passiert.

Patch

Die Patch-Funktion vergleicht die alten und neuen Knoten und bestimmt dann, welche Knoten geändert werden müssen. Nur diese Knoten müssen geändert werden, wodurch das DOM effizienter aktualisiert werden kann. Schauen wir uns zunächst den Code an.

Rückgabefunktionspatch (alterVnode, vnode, hydratisieren, nur entfernen) {
  /*vnode existiert nicht, rufen Sie den Destruction-Hook auf, um den Knoten zu löschen*/
  wenn (isUndef(vnode)) {
    if (isDef(alterVnode)) invokeDestroyHook(alterVnode)
    zurückkehren
  }

  let isInitialPatch = false
  const eingefügtVnodeQueue = []

  /*oldVnode existiert nicht, erstelle direkt einen neuen Knoten*/
  wenn (isUndef(alterVnode)) {
    // leeres Mount (wahrscheinlich als Komponente), neues Root-Element erstellen
    isInitialPatch = true
    createElm(vnode, eingefügteVnodeQueue)
  } anders {
    /*Markieren Sie, ob der alte VNode einen NodeType hat*/
    const isRealElement = isDef(alterVnode.nodeType)
    wenn (!isRealElement && sameVnode(oldVnode, vnode)) {
      // Vorhandenen Root-Knoten patchen
      /*Wenn es derselbe Knoten ist, ändern Sie den vorhandenen Knoten direkt*/
      patchVnode(alterVnode, vnode, eingefügteVnodeQueue, null, null, nur entfernen)
    } anders {
      wenn (istRealesElement) {
        // An ein reales Element anhängen
        // prüfen, ob es sich um serverseitig gerenderten Inhalt handelt und ob wir
        // eine erfolgreiche Hydratation.
        wenn (alterVnode.nodeType === 1 && alterVnode.hasAttribute(SSR_ATTR)) {
          /*Wenn der alte VNode ein vom Server gerendertes Element ist, wird „hydrating“ auf „true“ gesetzt*/
          alterVnode.removeAttribute(SSR_ATTR)
          hydratisierend = wahr
        }
        if (isTrue(hydratisierend)) {
          /*Muss in den echten Dom integriert werden*/
          wenn (hydrate(alterVnode, vnode, eingefügteVnodeQueue)) {
            /*Insert-Hook aufrufen*/
            invokeInsertHook(vnode, insertedVnodeQueue, true)
            gib alten Vnode zurück
          } sonst wenn (Prozess.Umgebung.NODE_ENV !== 'Produktion') {
            warnen(
              'Der clientseitig gerenderte virtuelle DOM-Baum stimmt nicht überein ' +
              'Server-gerenderter Inhalt. Dies wird wahrscheinlich durch falsche ' + verursacht
              'HTML-Markup, zum Beispiel das Verschachteln von Blockelementen innerhalb von ' +
              '<p> oder fehlendes <tbody>. Hydratation ablassen und durchführen ' +
              „Vollständiges clientseitiges Rendern.“
            )
          }
        }
        // Entweder nicht vom Server gerendert oder die Hydratisierung ist fehlgeschlagen.
        // einen leeren Knoten erstellen und ihn ersetzen
        /*Wenn das serverseitige Rendern oder das Zusammenführen in den echten Dom nicht fehlschlägt, erstellen Sie einen leeren VNode-Knoten, um ihn zu ersetzen*/
        alterVnode = emptyNodeAt(alterVnode)
      }

      // vorhandenes Element ersetzen
      /*Vorhandenes Element ersetzen*/
      const oldElm = alterVnode.elm
      const parentElm = nodeOps.parentNode(alteElm)

      // neuen Knoten erstellen
      erstelleElm(
        vKnoten,
        eingefügteVnodeQueue,
        // extrem seltener Sonderfall: nicht einfügen, wenn das alte Element in einem
        // Übergang verlassen. Passiert nur bei der Kombination von Übergang +
        // Keep-Alive + HOCs. (#4590)
        oldElm._leaveCb ? null : parentElm,
        nodeOps.nextSibling(alteElm)
      )

      // Platzhalterknotenelement des übergeordneten Knotens rekursiv aktualisieren
      wenn (isDef(vnode.parent)) {
        /*Der Stammknoten der Komponente wird ersetzt, wobei das übergeordnete Knotenelement durchlaufen und aktualisiert wird*/
        let Vorfahr = vnode.parent
        const patchable = ist Patchbar(vnode)
        während (Vorfahr) {
          für (lass i = 0; i < cbs.destroy.length; ++i) {
            cbs.destroy[i](Vorfahr)
          }
          ancestor.elm = vnode.elm
          if (patchbar) {
            /*Callback erstellen*/
            für (lass i = 0; i < cbs.create.length; ++i) {
              cbs.create[i](leererKnoten, Vorfahr)
            }
            //#6513
            // Einfüge-Hooks aufrufen, die möglicherweise durch Erstellungs-Hooks zusammengeführt wurden.
            // zB für Anweisungen, die den „inserted“-Hook verwenden.
            const insert = ancestor.data.hook.insert
            wenn (einfügen.zusammengeführt) {
              // Beginnen Sie bei Index 1, um ein erneutes Aufrufen des an der Komponente montierten Hooks zu vermeiden
              für (lass i = 1; i < insert.fns.length; i++) {
                einfügen.fns[i]()
              }
            }
          } anders {
            registerRef(Vorfahr)
          }
          Vorfahr = Vorfahr.Elternteil
        }
      }

      // alten Knoten zerstören
      wenn (isDef(übergeordneteElm)) {
        /*Alte Knoten entfernen*/
        removeVnodes([alterVnode], 0, 0)
      } sonst wenn (isDef(oldVnode.tag)) {
        /*Destroy-Hook aufrufen*/
        invokeDestroyHook(alterVnode)
      }
    }
  }

  /*Insert-Hook aufrufen*/
  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
  vnode.elm zurückgeben
}

Der Diff-Algorithmus von Vue vergleicht Knoten auf derselben Ebene, daher beträgt seine Zeitkomplexität nur O(n) und sein Algorithmus ist sehr effizient. Wir können dem Code auch entnehmen, dass sameVnode im Patch verwendet wird, um zu bestimmen, ob der alte Knoten und der neue Knoten derselbe Knoten sind. Wenn dies der Fall ist, wird weiter patchVnode ausgeführt, andernfalls wird ein neuer DOM erstellt und der alte DOM wird entfernt.

gleicherVnode

Als nächstes schauen wir uns an, wie sameVnode bestimmt, ob es sich bei zwei Knoten um den gleichen Knoten handelt.

/*
  Um zu bestimmen, ob zwei VNode-Knoten derselbe Knoten sind, müssen die folgenden Bedingungen erfüllt sein: Der Schlüssel ist derselbe, Tag (der Beschriftungsname des aktuellen Knotens) ist derselbe, isComment (ob es sich um einen Kommentarknoten handelt) ist derselbe, ob Daten (das dem aktuellen Knoten entsprechende Objekt, das bestimmte Dateninformationen enthält, ein VNodeData-Typ ist, Sie können sich auf die Dateninformationen im VNodeData-Typ beziehen) definiert sind, wenn das Tag <input> ist, der Typ muss derselbe sein*/
Funktion sameVnode (a, b) {
  zurückkehren (
    a.Schlüssel === b.Schlüssel && (
      (
        a.tag === b.tag &&
        a.istKommentar === b.istKommentar &&
        isDef(a.data) === isDef(b.data) &&
        gleicherEingabetyp(a, b)
      ) || (
        istTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        istUndef(b.asyncFactory.error)
      )
    )
  )
}

// Einige Browser unterstützen keine dynamische Typänderung für <input>
// Sie müssen also als unterschiedliche Knoten behandelt werden
/*
  Bestimmen Sie, ob der Typ derselbe ist, wenn das Tag <input> ist. Einige Browser unterstützen keine dynamische Änderung von <input>-Typen, daher werden sie als unterschiedliche Typen betrachtet*/
Funktion sameInputType (a, b) {
  wenn (a.tag !== 'Eingabe') true zurückgibt
  lass mich
  const TypA = isDef(i = a.data) && isDef(i = i.attrs) && i.Typ
  const TypB = isDef(i = b.data) && isDef(i = i.attrs) && i.Typ
  RückgabetypA === TypB || istTextInputType(TypA) && istTextInputType(TypB)
}

sameVnode bestimmt, ob zwei Knotenknoten identisch sind, indem es deren Schlüssel, Tag, Kommentarknoten und Dateninformationen vergleicht. Für das Eingabe-Tag wird eine separate Beurteilung vorgenommen, um die Kompatibilität mit verschiedenen Browsern sicherzustellen.

patchVnode

 // Diff-Algorithmus vergleicht Knoten Funktion patchVnode (
  alterVnode,
  vKnoten,
  eingefügteVnodeQueue,
  BesitzerArray,
  Index,
  Nur entfernen
) {
  /*Wenn zwei VNode-Knoten gleich sind, direkt zurückkehren*/
  wenn (alterVnode === vnode) {
    zurückkehren
  }

  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
  }

  // Element für statische Bäume wiederverwenden.
  // Beachten Sie, dass wir dies nur tun, wenn der Vnode geklont ist -
  // Wenn der neue Knoten nicht geklont ist, bedeutet dies, dass die Renderfunktionen
  // durch die Hot-Reload-API zurückgesetzt und wir müssen ein ordnungsgemäßes erneutes Rendern durchführen.
  /*
    Wenn sowohl der alte als auch der neue VNode statisch sind und ihre Schlüssel gleich sind (denselben Knoten darstellen),
    Und der neue VNode ist ein Klon oder mit once markiert (mit v-once-Attribut markiert, nur einmal gerendert),
    Dann müssen Sie nur noch Elm und ComponentInstance ersetzen.
  */
  wenn (isTrue(vnode.isStatic) &&
    isTrue(alterVnode.isStatic) &&
    vnode.key === alterVnode.key &&
    (istWahr(vnode.isCloned) || istWahr(vnode.isOnce))
  ) {
    vnode.componentInstance = alteVnode.componentInstance
    zurückkehren
  }

  // Führen Sie einige Komponenten-Hooks aus/*Wenn data.hook.prepatch vorhanden ist, führen Sie es zuerst aus*/
  lass mich
  const data = vnode.data
  wenn (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
    /*i = data.hook.prepatch, falls vorhanden, siehe "./create-component componentVNodeHooks". */
    i(alterV-Knoten, V-Knoten)
  }

  // Überprüfen Sie, ob die alten und neuen Knoten untergeordnete Knoten haben const oldCh = oldVnode.children
  const ch = vnode.children

  // Eigenschaftsaktualisierung, wenn (isDef(data) und isPatchable(vnode)) {
    // Nehmen Sie das Array der Attributaktualisierungen in cbs heraus [attrFn, classFn, ​​​​…]
    /*Update-Callback und Update-Hook aufrufen*/
    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)
  }

  // Bestimmen Sie, ob das Element if (isUndef(vnode.text)) { /*Wenn dieser VNode-Knoten keinen Text hat*/
    // Beide Parteien haben Kinder if (isDef(oldCh) && isDef(ch)) {
      /*Wenn sowohl der alte als auch der neue Knoten untergeordnete Knoten haben, führen Sie eine Diff-Operation an den untergeordneten Knoten durch und rufen Sie updateChildren auf*/
      wenn (alterKanal !== ch) updateChildren(elm, alterKanal, ch, eingefügteVnodeQueue, nurentfernen)
    } sonst wenn (isDef(ch)) {
      /*Wenn der alte Knoten keine untergeordneten Knoten hat und der neue Knoten untergeordnete Knoten hat, löschen Sie zuerst den Textinhalt von Elm und fügen Sie dann dem aktuellen Knoten untergeordnete Knoten hinzu*/
      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)) {
      /*Wenn der neue Knoten keine untergeordneten Knoten hat und der alte Knoten untergeordnete Knoten hat, entferne alle untergeordneten Knoten des Elements*/
      removeVnodes(alterKanal, 0, alterKanal.Länge - 1)
    } sonst wenn (isDef(alterVnode.text)) {
      /*Wenn sowohl der neue als auch der alte Knoten keine untergeordneten Knoten haben, wird nur der Text ersetzt. Da der neue Knotentext in dieser Logik nicht existiert, wird der Text von ele direkt entfernt*/
      nodeOps.setTextContent(ulme, '')
    }
  } sonst wenn (alterVnode.text !== vnode.text) {
    /*Wenn der Text der neuen und alten Knoten unterschiedlich ist, ersetzen Sie diesen Text direkt*/
    nodeOps.setTextContent(elm, vnode.text)
  }
  /*Postpatch-Hook aufrufen*/
  wenn (isDef(Daten)) {
    wenn (isDef(i = data.hook) und isDef(i = i.postpatch)) i(alterVnode, vnode)
  }
}

Der Prozess von patchVnode ist wie folgt:

  1. Wenn oldVnode und Vnode dasselbe Objekt sind, werden sie ohne Aktualisierung direkt zurückgegeben.
  2. Wenn der alte und der neue VNode beide statisch sind und ihre Schlüssel gleich sind (denselben Knoten darstellen) und der neue VNode geklont oder einmal markiert ist (mit dem Attribut „v-once“ markiert, nur einmal gerendert), müssen Sie nur elm und componentInstance ersetzen.
  3. Wenn vnode.text kein Textknoten ist und sowohl der alte als auch der neue Knoten über untergeordnete Knoten verfügen und die untergeordneten Knoten der alten und neuen Knoten unterschiedlich sind, wird an den untergeordneten Knoten ein Diff-Vorgang ausgeführt, der updateChildren aufruft. Dies ist auch der Kern des Diff-Vorgangs.
  4. Wenn der alte Knoten keine untergeordneten Knoten hat und der neue Knoten untergeordnete Knoten hat, löschen Sie zuerst den Textinhalt des alten Knoten-DOM und fügen Sie dann dem aktuellen DOM-Knoten untergeordnete Knoten hinzu
  5. Wenn der neue Knoten keine untergeordneten Knoten hat und der alte Knoten untergeordnete Knoten hat, werden alle untergeordneten Knoten des DOM-Knotens entfernt.
  6. Wenn sowohl der neue als auch der alte Knoten keine untergeordneten Knoten haben, wird nur der Text ersetzt.

Kinder aktualisieren

Der DOM unserer Seite ist eine Baumstruktur. Die oben erwähnte patchVnode-Methode verwendet dasselbe DOM-Element wieder. Wenn sowohl das neue als auch das alte VNnode-Objekt untergeordnete Elemente haben, wie sollen wir die wiederverwendeten Elemente vergleichen? Dies ist, was unsere Methode updateChildren macht.

/*
  Diff-Core-Methode, Vergleichsoptimierung*/
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)) {
      /*Nach rechts bewegen*/
      oldStartVnode = oldCh[++oldStartIdx] // Vnode wurde nach links verschoben
    } sonst wenn (isUndef(oldEndVnode)) {
      /*Nach links bewegen*/
      oldEndVnode = alterCh[--oldEndIdx]
    } sonst wenn (gleicherVnode(alterStartVnode, neuerStartVnode)) {
      /*Die ersten vier Fälle treten tatsächlich auf, wenn der Schlüssel angegeben ist. Wenn festgestellt wird, dass es sich um denselben VNode handelt, patchen Sie Vnode direkt und vergleichen Sie die beiden Kopfknoten von oldCh und newCh jeweils 2 * 2 = 4 Fälle */
      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 {
      /*
        Generieren Sie eine Hash-Tabelle mit dem Schlüssel, der dem Schlüssel des alten VNode entspricht (sie wird nur generiert, wenn undefined zum ersten Mal auftritt, und ebnet auch den Weg für die spätere Erkennung doppelter Schlüsselwerte).
        Beispielsweise sieht childre so aus [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}] beginIdx = 0 endIdx = 2  
        Das Ergebnis ist {key0: 0, key1: 1, key2: 2}
      */
      wenn (isUndef(alterKeyToIdx)) alterKeyToIdx = createKeyToOldIdx(alterCh, alteStartIdx, alteEndIdx)

      /*Wenn der neue VNode-Knoten newStartVnode einen Schlüssel hat und dieser Schlüssel in oldVnode gefunden werden kann, dann gib den idxInOld dieses Knotens zurück (das heißt die Knotennummer, Index)*/
      idxInOld = isDef(neuerStartVnode.key)
        ? oldKeyToIdx[neuerStartVnode.key]
        : findIdxInOld(neuerStartVnode, alterCh, alteStartIdx, alteEndIdx)
      if (isUndef(idxInOld)) { // Neues Element
        /*NewStartVnode hat keinen Schlüssel oder der Schlüssel wurde im alten Knoten nicht gefunden, also erstellen Sie einen neuen Knoten*/
        createElm(neuerStartVnode, eingefügteVnodeQueue, übergeordneteElm, alterStartVnode.elm, false, neuerCh, neueStartIdx)
      } anders {
        /*Holen Sie sich den alten Knoten mit dem gleichen Schlüssel*/
        vnodeToMove = alterCh[idxInOld]
        wenn (sameVnode(vnodeToMove, newStartVnode)) {
          /*Wenn der neue VNode und der erhaltene Knoten mit demselben Schlüssel derselbe VNode sind, patchen Sie Vnode*/
          patchVnode(ZuVerschiebender vnode, neuerStartVnode, eingefügteVnodeQueue, neuerCh, neueStartIdx)
          /*Da patchVnode eingegeben wurde, wird dem alten Knoten undefiniert zugewiesen. Wenn es einen neuen Knoten mit demselben Schlüssel wie diesen Knoten gibt, kann erkannt und darauf hingewiesen werden, dass ein doppelter Schlüssel vorhanden ist*/
          oldCh[idxInOld] = undefiniert
          /*Wenn es ein Flag canMove gibt, kann es direkt vor dem realen Dom-Knoten eingefügt werden, der oldStartVnode entspricht*/
          kann verschieben und nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
        } anders {
          // gleicher Schlüssel, aber anderes Element. Als neues Element behandeln
          /*Wenn der neue VNode nicht derselbe VNode ist wie der VNode mit demselben Schlüssel (beispielsweise sind die Tags unterschiedlich oder die Eingabe-Tags haben unterschiedliche Typen), erstellen Sie einen neuen Knoten*/
          createElm(neuerStartVnode, eingefügteVnodeQueue, übergeordneteElm, alterStartVnode.elm, false, neuerCh, neueStartIdx)
        }
      }
      neuerStartVnode = neuerCh[++neueStartIdx]
    }
  }
  wenn (alteStartIdx > alteEndIdx) {
    /*Wenn nach Abschluss aller Vergleiche oldStartIdx > oldEndIdx ist, bedeutet dies, dass die alten Knoten durchlaufen wurden und es mehr neue als alte Knoten gibt. Daher müssen die zusätzlichen neuen Knoten einzeln erstellt und dem realen Dom hinzugefügt werden*/
    refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
    addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, eingefügteVnodeQueue)
  } sonst wenn (neueStartIdx > neueEndIdx) {
    /*Wenn nach Abschluss aller Vergleiche newStartIdx > newEndIdx ist, bedeutet dies, dass der neue Knoten durchlaufen wurde und mehr alte als neue Knoten vorhanden sind. Zu diesem Zeitpunkt müssen die redundanten alten Knoten aus dem realen Dom entfernt werden*/
    removeVnodes(alterCh, alteStartIdx, alteEndIdx)
  }
}

Das Folgende ist eine Kopie von Artikeln anderer Blogger, die meiner Meinung nach gut geschrieben sind: github.com/liutao/vue2…


Auf den ersten Blick kann dieser Codeblock etwas verwirrend sein. Der konkrete Inhalt ist eigentlich nicht kompliziert. Werfen wir zunächst einen allgemeinen Blick auf den gesamten Beurteilungsprozess und gehen ihn dann anhand mehrerer Beispiele im Detail durch.

oldStartIdx, newStartIdx, oldEndIdx und newEndIdx sind alles Zeiger. Ich glaube, jeder weiß, worauf sich jeder bezieht. Während des gesamten Vergleichsvorgangs werden wir den Zeiger weiter bewegen.

oldStartVnode, newStartVnode, oldEndVnode und newEndVnode entsprechen eins zu eins den obigen Zeigern und sind die VNode-Knoten, auf die sie zeigen.

Die While-Schleife wird angehalten, nachdem die Durchquerung von oldCh oder newCh abgeschlossen ist, andernfalls wird der Schleifenprozess weiter ausgeführt. Der gesamte Prozess gliedert sich in folgende Situationen:

1. Wenn oldStartVnode nicht definiert ist, wird der Startzeiger der oldCh-Array-Traversierung um eine Position zurück verschoben.

  wenn (isUndef(alterStartVnode)) {
    oldStartVnode = oldCh[++oldStartIdx] // Vnode wurde nach links verschoben
  }

Hinweis: Siehe den siebten Fall. Derselbe Schlüsselwert kann auf „undefiniert“ gesetzt sein.

2. Wenn oldEndVnode nicht definiert ist, wird der Startzeiger der oldCh-Array-Traversierung um eine Position nach vorne verschoben.

  sonst wenn (isUndef(oldEndVnode)) {
    oldEndVnode = alterCh[--oldEndIdx]
  } 

Hinweis: Siehe den siebten Fall. Derselbe Schlüsselwert kann auf „undefiniert“ gesetzt sein.

3. sameVnode(oldStartVnode, newStartVnode), hier wird bestimmt, ob die Objekte, auf die die beiden Array-Startzeiger zeigen, wiederverwendet werden können. Wenn es true zurückgibt, wird zuerst die Methode patchVnode aufgerufen, um das DOM-Element wiederzuverwenden und die untergeordneten Elemente rekursiv zu vergleichen. Anschließend werden die Startzeiger von oldCh und newCh jeweils um eine Position zurückgesetzt.

  sonst wenn (gleicherVnode(alterStartVnode, neuerStartVnode)) {
    patchVnode(alterStartVnode, neuerStartVnode, eingefügteVnodeQueue)
    alterStartVnode = alterCh[++alteStartIdx]
    neuerStartVnode = neuerCh[++neueStartIdx]
  }

4. sameVnode(oldEndVnode, newEndVnode), hier wird bestimmt, ob die Objekte, auf die die beiden Array-Endzeiger zeigen, wiederverwendet werden können. Wenn true zurückgegeben wird, wird zuerst die Methode patchVnode aufgerufen, um das DOM-Element wiederzuverwenden und die untergeordneten Elemente rekursiv zu vergleichen. Anschließend werden die Endzeiger von oldCh und newCh jeweils um eine Position nach vorne verschoben.

  sonst wenn (gleicherVnode(alterVnodeEnde, neuerVnodeEnde)) {
    patchVnode(alterEndVnode, neuerEndVnode, eingefügteVnodeQueue)
    oldEndVnode = alterCh[--oldEndIdx]
    neuerEndVnode = neuerCh[--newEndIdx]
  } 

5. sameVnode(oldStartVnode, newEndVnode), hier wird bestimmt, ob das Objekt, auf das der Startzeiger oldCh zeigt, und das Objekt, auf das der Endzeiger newCh zeigt, wiederverwendet werden können. Wenn es true zurückgibt, wird zuerst die Methode patchVnode aufgerufen, um das DOM-Element wiederzuverwenden und die untergeordneten Elemente rekursiv zu vergleichen. Da das wiederverwendete Element das Element ist, auf das der Endzeiger in newCh zeigt, wird es vor oldEndVnode.elm eingefügt. Abschließend bewegt sich der Startzeiger von oldCh um eine Position zurück und der Startzeiger von newCh um eine Position vorwärts.

  sonst wenn (sameVnode(oldStartVnode, newEndVnode)) { // Vnode nach rechts verschoben
    patchVnode(alterStartVnode, neuerEndVnode, eingefügteVnodeQueue)
    kann verschieben && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
    alterStartVnode = alterCh[++alteStartIdx]
    neuerEndVnode = neuerCh[--newEndIdx]
  }

6. sameVnode(oldEndVnode, newStartVnode), hier wird bestimmt, ob das Objekt, auf das der Endzeiger von oldCh zeigt, und das Objekt, auf das der Startzeiger von newCh zeigt, wiederverwendet werden können. Wenn es true zurückgibt, wird zuerst die Methode patchVnode aufgerufen, um das DOM-Element wiederzuverwenden und die untergeordneten Elemente rekursiv zu vergleichen. Da das wiederverwendete Element das Element ist, auf das der Startzeiger in newCh zeigt, wird es vor oldStartVnode.elm eingefügt. Schließlich bewegt sich der Endzeiger von oldCh um eine Position vorwärts und der Startzeiger von newCh um eine Position rückwärts.

  sonst wenn (sameVnode(oldEndVnode, newStartVnode)) { // Vnode nach links verschoben
    patchVnode(alterEnd-Vnode, neuerStart-Vnode, eingefügteVnodeQueue)
    kann verschieben und nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
    oldEndVnode = alterCh[--oldEndIdx]
    neuerStartVnode = neuerCh[++neueStartIdx]
  }

7. Wenn keine der oben genannten sechs Bedingungen erfüllt ist, gehen Sie hierhin. Bei den vorherigen Vergleichen handelt es sich um Vergleiche von Kopf- und Schwanzkombinationen. Die Situation hier ist etwas komplizierter. Tatsächlich basiert sie hauptsächlich auf der Wiederverwendung von Elementen entsprechend dem Schlüsselwert.

① Durchlaufen Sie das oldCh-Array, suchen Sie das Objekt mit dem Schlüssel und generieren Sie ein neues Objekt oldKeyToIdx mit dem Schlüssel als Schlüssel und dem Indexwert als Wert.

wenn (isUndef(alterKeyToIdx)) alterKeyToIdx = createKeyToOldIdx(alterCh, alteStartIdx, alteEndIdx)
Funktion createKeyToOldIdx (Kinder, beginIdx, endIdx) {
  lass ich, Schlüssel
  const map = {}
  für (i = beginIdx; i <= endIdx; ++i) {
    Schlüssel = Kinder[i].Schlüssel
    wenn (isDef(Schlüssel)) map[Schlüssel] = i
  }
  Rückfahrkarte
}

② Überprüfen Sie, ob newStartVnode einen Schlüsselwert hat, und überprüfen Sie, ob oldKeyToIdx denselben Schlüssel hat.

  idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null

③ Wenn newStartVnode keinen Schlüssel hat oder oldKeyToIdx nicht denselben Schlüssel hat, wird die Methode createElm aufgerufen, um ein neues Element zu erstellen, und der Startindex von newCh wird um eine Position nach hinten verschoben.

  if (isUndef(idxInOld)) { // Neues Element
    createElm(neuerStartVnode, eingefügteVnodeQueue, parentElm, alterStartVnode.elm)
    neuerStartVnode = neuerCh[++neueStartIdx]
  } 

④ elmToMove speichert das zu verschiebende Element. Wenn sameVnode(elmToMove, newStartVnode) true zurückgibt, bedeutet dies, dass es wiederverwendet werden kann. Zu diesem Zeitpunkt wird zuerst die Methode patchVnode aufgerufen, um das DOM-Element wiederzuverwenden und die untergeordneten Elemente rekursiv zu vergleichen. Das relative Element in oldCh wird auf undefiniert zurückgesetzt, und dann wird das aktuelle Element vor oldStartVnode.elm eingefügt, und der Startindex von newCh wird um eine Position nach hinten verschoben. Wenn sameVnode(elmToMove, newStartVnode) beispielsweise „false“ zurückgibt, sind die Tag-Namen unterschiedlich, die Methode createElm wird aufgerufen, um ein neues Element zu erstellen, und der Startindex von newCh wird um eine Position nach hinten verschoben.

  elmToMove = alterCh[idxInOld]
  wenn (sameVnode(elmToMove, newStartVnode)) {
    patchVnode(elmToMove, neuerStartVnode, eingefügteVnodeQueue)
    oldCh[idxInOld] = undefiniert
    kann verschieben und nodeOps.insertBefore(parentElm, neuerStartVnode.elm, alterStartVnode.elm)
    neuerStartVnode = neuerCh[++neueStartIdx]
  } anders {
    // gleicher Schlüssel, aber anderes Element. Als neues Element behandeln
    createElm(neuerStartVnode, eingefügteVnodeQueue, parentElm, alterStartVnode.elm)
    neuerStartVnode = neuerCh[++neueStartIdx]
  }

Oben finden Sie ausführliche Informationen zur Verwendung des Vue-Diff-Algorithmus. Weitere Informationen zum Vue-Diff-Algorithmus finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM!

Das könnte Sie auch interessieren:
  • Kennen Sie den virtuellen DOM- und Diff-Algorithmus von Vue?
  • Ein kurzer Vortrag über den Diff-Algorithmus in Vue
  • 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?

<<:  Detailliertes Tutorial zur Konfigurationsmethode der kostenlosen Installationsversion von Mysql 5.7.19 (64-Bit)

>>:  CentOS 6.5-Konfiguration: SSH-Anmeldung ohne Schlüssel zur Ausführung des PSSH-Befehls – Erläuterung

Artikel empfehlen

Detaillierte Erklärung, wie Angular mit unerwarteten Ausnahmefehlern umgeht

Vorne geschrieben Unabhängig davon, wie gut der C...

Eine SQL-Anweisung schließt die MySQL-Deduplizierung ab und behält eine

Als ich vor einigen Tagen an einer Anforderung ar...

Detailliertes Tutorial zur Installation von SonarQube mit Docker

Inhaltsverzeichnis 1. Ziehen Sie das Bild 1.1 Zie...

Installationstutorial für MySQL 5.7 unter CentOS 7

1. Laden Sie das offizielle MySQL Yum Repository ...

Detaillierte Erklärung der berechneten Eigenschaften in Vue

Inhaltsverzeichnis Interpolationsausdrücke Method...

Fünf Verzögerungsmethoden für die MySQL-Zeitblindinjektion

Fünf Verzögerungsmethoden für die MySQL-Zeitblind...

So installieren Sie eine ISO-Datei im Linux-System

Wie installiere ich ISO-Dateien unter einem Linux...

Detaillierte Erläuterung des Linux-Befehls zur Änderung des Hostnamens

Linux-Befehl zum Ändern des Hostnamens 1. Wenn Si...