Heim >Web-Frontend >js-Tutorial >Einführung in das Prinzip des virtuellen Dom-Vergleichs in Vue (Beispielerklärung)
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!
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:
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, childVnode
rekursiv ein neuer Patching-Prozess gestartet wird.
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
-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; }PatchDie 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
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
auf, um es zu ändern. setAttribute()
auf, um es zu löschen. removeAttribute()
Urteil befindet, mit dem beurteilt wird, ob es sich im Attributwörterbuch des booleschen Typs befindet. booleanAttrsDict[key]
zB: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:, wenn Sie die automatische Wiedergabe deaktivieren möchten, müssen Sie dieses Attribut entfernen.
<video autoplay></video>
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: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!
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
), 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: b
b
a,c,d,e,b,f
c,d,e
Das fünfte Mal ist nicht ähnlich wird direkt in den Header „Confirm the dom sequence“ eingefügt. Nachdem der Knoten
e
e
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: g
a,e,g,c,d,b,f
c,d
h
a,e,g,h,c,d,b,f
c,d
ist. Das neue Byte-Punkt-Array (oldCh) wird durchlaufen (
上面说了这么多都是没有key的情况,说添加了:key
可以优化v-for
的性能,到底是怎么回事呢?因为v-for
大部分情况下生成的都是相同tag
的标签,如果没有key标识,那么相当于每次头头比较都能成功。你想想如果你往v-for
绑定的数组头部push数据,那么整个dom将全部刷新一遍(如果数组每项内容都不一样),那加了key
会有什么帮助呢?这边引用一张图:
有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!