Heim  >  Artikel  >  Web-Frontend  >  Einführung in das Prinzip des virtuellen Dom-Vergleichs in Vue (Beispielerklärung)

Einführung in das Prinzip des virtuellen Dom-Vergleichs in Vue (Beispielerklärung)

不言
不言nach vorne
2019-02-20 13:42:554014Durchsuche

Der Inhalt dieses Artikels ist eine Einführung in das Vergleichsprinzip des virtuellen Doms in Vue (Erklärung mit Beispielen). Ich hoffe, dass er für Sie hilfreich ist.

Lassen Sie uns zunächst darüber sprechen, warum es eine Phase des virtuellen DOM-Vergleichs gibt. Wir wissen, dass Vue eine datengesteuerte Ansicht ist (Änderungen an den Daten führen zu Änderungen in der Ansicht), aber Sie stellen fest, dass dies bei bestimmten Daten der Fall ist Änderungen, die Ansicht ist teilweise Wie kann man die den Daten entsprechende Ansicht genau finden und durch Aktualisieren aktualisieren, anstatt das Ganze neu zu rendern? Dann müssen Sie die DOM-Struktur vor und nach der Datenänderung abrufen, die Unterschiede finden und aktualisieren!

Der virtuelle Dom ist im Wesentlichen ein einfaches Objekt, das aus dem realen Dom extrahiert wird. Genauso wie ein einfaches P mehr als 200 Attribute enthält, aber das einzige, das wirklich benötigt wird, möglicherweise tagName ist, sodass die direkte Operation am realen Dom die Leistung stark beeinträchtigt!

Einführung in das Prinzip des virtuellen Dom-Vergleichs in Vue (Beispielerklärung)

Der vereinfachte virtuelle Knoten (vnode) enthält ungefähr die folgenden Attribute:

{
  tag: 'p',       // 标签名
  data: {},         // 属性数据,包括class、style、event、props、attrs等
  children: [],     // 子节点数组,也是vnode结构
  text: undefined,  // 文本
  elm: undefined,   // 真实dom
  key: undefined    // 节点标识
}

Der Vergleich des virtuellen Doms dient dazu, den neuen Knoten herauszufinden (vnode) und den alten Knoten (oldVnode) und patchen Sie dann den Unterschied. Der allgemeine Prozess ist wie folgt:

Einführung in das Prinzip des virtuellen Dom-Vergleichs in Vue (Beispielerklärung)

Der gesamte Prozess ist relativ einfach. Wenn der alte und der neue Knoten nicht ähnlich sind, erstellen Sie das DOM direkt auf der Grundlage des neuen Knotens Ähnlich, vergleichen Sie zuerst die Daten, einschließlich Klasse und Stil, Ereignis, Requisiten, Attribute usw., rufen Sie die entsprechende Aktualisierungsfunktion auf und vergleichen Sie dann die untergeordneten Knoten Diff-Algorithmus, der der Schwerpunkt und die Schwierigkeit dieses Artikels sein sollte.

Es ist erwähnenswert, dass während des

Prozesses, wenn ähnliche Children Compare gefunden werden, childVnoderekursiv ein neuer Patching-Prozess gestartet wird.

Quellcode-Analyse

Dieses Mal ist die Quellcode-Analyse prägnanter geschrieben. Wenn ich zu viel schreibe, bin ich nicht bereit, es zu lesen (┬_┬)

Start

Schauen wir uns zunächst die Funktion

an: Die Funktion patch()

function patch (oldVnode, vnode) {
  var elm, parent;
  if (sameVnode(oldVnode, vnode)) {
    // 相似就去打补丁(增删改)
    patchVnode(oldVnode, vnode);
  } else {
    // 不相似就整个覆盖
    elm = oldVnode.elm;
    parent = api.parentNode(elm);
    createElm(vnode);
    if (parent !== null) {
      api.insertBefore(parent, vnode.elm, api.nextSibling(elm));
      removeVnodes(parent, [oldVnode], 0, 0);
    }
  }
  return vnode.elm;
}

empfängt zwei Parameter, den alten und den neuen vnode. Es gibt einen großen Unterschied zwischen den beiden übergebenen Parametern : Das patch() von oldVnode zeigt auf den realen Dom, während das elm von vnode undefiniert ist ... aber nach dem Übergeben der elm-Methode zeigt das patch() von vnode auch auf diesen (aktualisierten) realen Dom . elm

Die

-Methode zur Bestimmung, ob der alte und der neue V-Knoten ähnlich sind, ist sehr einfach. Sie besteht darin, zu vergleichen, ob sameVnode()Tag und Schlüssel konsistent sind.

function sameVnode (a, b) {
  return a.key === b.key && a.tag === b.tag;
}
Patch

Die Lösung für

Inkonsistenz zwischen alten und neuen Vnodes ist sehr einfach, das heißt, erstellen Sie einen echten Dom basierend auf Vnode und fügen Sie stattdessen das DOM-Dokument ein in oldVnode. elm

Die Verarbeitung der

Konsistenz zwischen alten und neuen Vnodes ist das Patching, das wir bereits oft erwähnt haben. Was genau ist Patchen? Schauen Sie sich einfach die -Methode an: patchVnode()

function patchVnode (oldVnode, vnode) {
  // 新节点引用旧节点的dom
  let elm = vnode.elm = oldVnode.elm;
  const oldCh = oldVnode.children;
  const ch = vnode.children;

  // 调用update钩子
  if (vnode.data) {
    updateAttrs(oldVnode, vnode);
    updateClass(oldVnode, vnode);
    updateEventListeners(oldVnode, vnode);
    updateProps(oldVnode, vnode);
    updateStyle(oldVnode, vnode);
  }

  // 判断是否为文本节点
  if (vnode.text == undefined) {
    if (isDef(oldCh) && isDef(ch)) {
      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue)
    } else if (isDef(ch)) {
      if (isDef(oldVnode.text)) api.setTextContent(elm, '')
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
    } else if (isDef(oldCh)) {
      removeVnodes(elm, oldCh, 0, oldCh.length - 1)
    } else if (isDef(oldVnode.text)) {
      api.setTextContent(elm, '')
    }
  } else if (oldVnode.text !== vnode.text) {
    api.setTextContent(elm, vnode.text)
  }
}
Beim Patchen werden tatsächlich verschiedene

-Funktionen aufgerufen, um verschiedene Attribute des echten Doms zu aktualisieren. Jede Aktualisierungsfunktion ist ähnlich. Nehmen Sie updateXXX() als Beispiel: updateAttrs()

function updateAttrs (oldVnode, vnode) {
  let key, cur, old
  const elm = vnode.elm
  const oldAttrs = oldVnode.data.attrs || {}
  const attrs = vnode.data.attrs || {}

  // 更新/添加属性
  for (key in attrs) {
    cur = attrs[key]
    old = oldAttrs[key]
    if (old !== cur) {
      if (booleanAttrsDict[key] && cur == null) {
        elm.removeAttribute(key)
      } else {
        elm.setAttribute(key, cur)
      }
    }
  }
  // 删除新节点不存在的属性
  for (key in oldAttrs) {
    if (!(key in attrs)) {
      elm.removeAttribute(key)
    }
  }
}
Die allgemeine Idee der Aktualisierungsfunktion des Attributs (

) lautet: Attribute

  • Durchlaufen Sie das Vnode-Attribut und rufen Sie

    auf, um es zu ändern. setAttribute()

  • Durchlaufen Sie das OldVnode-Attribut. Wenn es sich nicht im Vnode-Attribut befindet, Rufen Sie

    auf, um es zu löschen. removeAttribute()

Sie werden feststellen, dass sich darin ein

Urteil befindet, mit dem beurteilt wird, ob es sich im Attributwörterbuch des booleschen Typs befindet. booleanAttrsDict[key]

['allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'compact', 'controls', 'declare', ...]
zB:

, wenn Sie die automatische Wiedergabe deaktivieren möchten, müssen Sie dieses Attribut entfernen. <video autoplay></video>

Nachdem alle Daten verglichen wurden, ist es Zeit, die untergeordneten Knoten zu vergleichen. Bestimmen Sie zunächst, ob es sich bei dem aktuellen V-Knoten um einen Textknoten handelt. Wenn es sich um einen Elementknoten handelt, muss er in drei Situationen berücksichtigt werden:

  • Sowohl der alte als auch der neue Knoten haben Kinder. Geben Sie dann den Vergleich der untergeordneten Knoten ein (Diff-Algorithmus).

  • Der neue Knoten hat Kinder, aber der alte Wenn kein Knoten vorhanden ist, erstellen Sie einen Dom-Knoten in einer Schleife.

  • Der neue Knoten hat keine untergeordneten Knoten, der alte Knoten jedoch. Löschen Sie dann den Dom-Knoten in einer Schleife.

Die letzten beiden Fälle sind relativ einfach. Wir analysieren direkt den ersten Fall, den Vergleich von

untergeordneten Knoten.

Diff-Algorithmus

Dieser Teil des Unterknotenvergleichs enthält viel Code. Lassen Sie uns zuerst über das Prinzip sprechen und dann den Code veröffentlichen. Schauen Sie sich zunächst ein Bild an, in dem untergeordnete Knoten verglichen werden:

Einführung in das Prinzip des virtuellen Dom-Vergleichs in Vue (Beispielerklärung)

oldCh und newCh in der Abbildung stellen die alten und neuen untergeordneten Knotenarrays dar. Sie haben ihre eigenen Kopf- und Schwanzzeiger oldStartIdx, oldEndIdx, newStartIdx, newEndIdx Im Array gespeichert ist vnode. Zum leichteren Verständnis verwenden wir stattdessen a, b, c, d usw., die Vnode-Objekte verschiedener Beschriftungstypen (p, span, p) darstellen.

Der Vergleich von untergeordneten Knoten ist im Wesentlichen ein Schleifenvergleich von Kopf- und Schwanzknoten. Das Zeichen für das Ende der Schleife ist: Das alte untergeordnete Knotenarray oder das neue untergeordnete Knotenarray wurde durchlaufen (d. h. oldStartIdx > oldEndIdx || newStartIdx > newEndIdx). Werfen Sie einen groben Blick auf den Zyklusprozess:

  • Der erste Schritt Vergleichen Sie Kopf an Kopf . Wenn sie ähnlich sind, bewegen sich die alten und neuen Kopfzeiger rückwärts (d. h. oldStartIdx++ && newStartIdx++), das echte DOM bleibt unverändert, und wenn sie nicht ähnlich sind, wird der zweite Schritt eingegeben.

  • Schritt 2 Schwanz mit Schwanz vergleichen. Wenn sie ähnlich sind, bewegen sich die alten und neuen Endzeiger vorwärts (d. h. oldEndIdx-- && newEndIdx--), das echte DOM bleibt unverändert, und wenn sie nicht ähnlich sind, wird der dritte Schritt eingegeben.

  • Schritt 3 Kopf-zu-Schwanz vergleichen. Wenn sie ähnlich sind, bewegt sich der alte Kopfzeiger nach hinten und der neue Schwanzzeiger nach vorne (d. h. oldStartIdx++ && newEndIdx-- Der Kopf in der unbestätigten DOM-Sequenz wird an das Ende verschoben und der nächste Zyklus wird eingegeben nicht ähnlich sind, wird der vierte Schritt eingegeben.

  • Schritt 4 Schwanz und Kopf vergleichen. Wenn sie ähnlich sind, bewegt sich der alte Schwanzzeiger vorwärts und der neue Kopfzeiger rückwärts (d. h. oldEndIdx-- && newStartIdx++ Der Schwanz in der unbestätigten DOM-Sequenz wird an den Anfang verschoben und der nächste Zyklus wird eingegeben nicht ähnlich sind, gehen Sie zum fünften Schritt.

  • Schritt 5: Wenn der Knoten einen Schlüssel hat und derselbe Vnode im alten untergeordneten Knotenarray gefunden wird (Tag und Schlüssel sind konsistent), dann verschieben Sie seinen Dom an den Kopf des aktuelle reale Dom-Sequenz. Der neue Kopfzeiger bewegt sich rückwärts (d. h. newStartIdx++); andernfalls wird der dem V-Knoten entsprechende Dom (vnode[newStartIdx].elm) in den Kopf der aktuellen realen Dom-Sequenz eingefügt und der neue Kopfzeiger bewegt sich rückwärts (d. h. newStartIdx++).

Schauen wir uns zunächst die Situation ohne Schlüssel an und fügen Sie eine Animation ein, um sie klarer zu sehen!

Einführung in das Prinzip des virtuellen Dom-Vergleichs in Vue (Beispielerklärung)

Ich glaube, dass Sie nach dem Lesen des Bildes die Essenz des Diff-Algorithmus besser verstehen werden. Der gesamte Prozess ist relativ einfach. Im Bild oben wurden insgesamt 6 Zyklen eingegeben, die jede Situation betreffen. Beschreiben wir jede einzelne:

  • Das erste Mal ist ähnlich (beide sind a), und das dom ändert sich nicht, sowohl der alte als auch der neue Kopfzeiger bewegen sich nach hinten. Nachdem der Knoten a bestätigt wurde, lautet die tatsächliche Dom-Sequenz: a,b,c,d,e,f und die unbestätigte Dom-Sequenz lautet: b,c,d,e,f

  • Das zweite Mal ist ähnlich (beide sind).

    ), der Dom ändert sich nicht und sowohl der alte als auch der neue Endzeiger werden nach vorne verschoben. Nachdem der Knoten f bestätigt wurde, lautet die tatsächliche Dom-Sequenz: f und die unbestätigte Dom-Sequenz lautet: a,b,c,d,e,f. Der Kopf in der aktuell verbleibenden echten DOM-Sequenz wird zum Ende verschoben, dem alten Der Kopfzeiger wird nach hinten und der neue Schwanzzeiger nach vorne bewegt. b,c,d,e Nachdem der Knoten bestätigt wurde, lautet die tatsächliche Dom-Sequenz:

    und die unbestätigte Dom-Sequenz lautet:
  • ). Der Schwanzzeiger wird nach vorne bewegt und der neue Kopfzeiger wird nach hinten bewegt. Nachdem der Knoten
  • bestätigt wurde, lautet die tatsächliche Dom-Sequenz:

    und die unbestätigte Dom-Sequenz lautet: bba,c,d,e,b,fc,d,e Das fünfte Mal ist nicht ähnlich wird direkt in den Header „Confirm the dom sequence“ eingefügt. Nachdem der Knoten

    eingefügt wurde, lautet die tatsächliche Dom-Sequenz:
  • und die unbestätigte Dom-Sequenz:
  • ee Das sechste Mal ist nicht ähnlich Es wird direkt in den Header „Confirm the dom sequence“ eingefügt. Nachdem der Knoten a,e,c,d,b,f eingefügt wurde, lautet die tatsächliche Dom-Sequenz: c,d, und die unbestätigte Dom-Sequenz lautet:

  • Aber nach dem Ende der Schleife gibt es sie Zwei zu berücksichtigende Situationen: ga,e,g,c,d,b,fc,d

    Das neue Bytepunkt-Array (newCh) wird durchlaufen (
  • ). Dann müssen Sie den gesamten überflüssigen alten Dom (
  • ) löschen, der im obigen Beispiel

    ha,e,g,h,c,d,b,fc,d ist. Das neue Byte-Punkt-Array (oldCh) wird durchlaufen (

    ). Dann müssen Sie alle zusätzlichen neuen Dom hinzufügen (
  • ).

上面说了这么多都是没有key的情况,说添加了:key可以优化v-for的性能,到底是怎么回事呢?因为v-for大部分情况下生成的都是相同tag的标签,如果没有key标识,那么相当于每次头头比较都能成功。你想想如果你往v-for绑定的数组头部push数据,那么整个dom将全部刷新一遍(如果数组每项内容都不一样),那加了key会有什么帮助呢?这边引用一张图:

Einführung in das Prinzip des virtuellen Dom-Vergleichs in Vue (Beispielerklärung)

key的情况,其实就是多了一步匹配查找的过程。也就是上面循环流程中的第五步,会尝试去旧子节点数组中找到与当前新子节点相似的节点,减少dom的操作!

有兴趣的可以看看代码:

function updateChildren (parentElm, oldCh, newCh) {
  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, elmToMove, before

  while (oldStartIdx  oldEndIdx) {
    before = isUndef(newCh[newEndIdx+1]) ? null : newCh[newEndIdx + 1].elm
    addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  } else if (newStartIdx > newEndIdx) {
    removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
}

Das obige ist der detaillierte Inhalt vonEinführung in das Prinzip des virtuellen Dom-Vergleichs in Vue (Beispielerklärung). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

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