Vorwort Unsere tägliche Arbeit als Frontend-Entwickler besteht darin, Daten auf der Seite darzustellen und Benutzerinteraktionen zu handhaben. In Vue wird die Seite neu gerendert, wenn sich die Daten ändern. Beispielsweise zeigen wir auf der Seite eine Zahl mit einer Klickschaltfläche daneben an. Jedes Mal, wenn wir auf die Schaltfläche klicken, erhöht sich die auf der Seite angezeigte Zahl um eins. Wie können wir das erreichen?
Daher ist es nicht einfach, ein responsives System zu implementieren. Lassen Sie uns die hervorragenden Ideen in Vue kennenlernen, indem wir den Vue-Quellcode kombinieren ~ 1. Schlüsselelemente eines reaktionsfähigen Systems 1. So überwachen Sie DatenänderungenOffensichtlich ist es sehr umständlich, Datenänderungen durch die Überwachung aller Benutzerinteraktionsereignisse zu erhalten, und einige Datenänderungen werden möglicherweise nicht von Benutzern ausgelöst. Wie überwacht Vue also Datenänderungen? ——Objekt.defineProperty Warum kann die Methode Object.defineProperty Datenänderungen überwachen? Diese Methode kann direkt eine neue Eigenschaft für ein Objekt definieren oder eine vorhandene Eigenschaft eines Objekts ändern und das Objekt zurückgeben. Schauen wir uns zunächst die Syntax an: Object.defineProperty(Objekt, Eigenschaft, Deskriptor) // obj ist das übergebene Objekt, prop ist die zu definierende oder zu ändernde Eigenschaft, descriptor ist der Eigenschaftsdeskriptor Der Kern ist hierbei der Deskriptor, der über viele optionale Schlüsselwerte verfügt. Was uns hier am meisten interessiert, sind get und set. Get ist eine Getter-Methode, die für eine Eigenschaft bereitgestellt wird. Wenn wir auf die Eigenschaft zugreifen, wird die Getter-Methode ausgelöst; set ist eine Setter-Methode, die für eine Eigenschaft bereitgestellt wird. Wenn wir die Eigenschaft ändern, wird die Setter-Methode ausgelöst. Kurz gesagt, sobald ein Datenobjekt über Getter und Setter verfügt, können wir seine Änderungen einfach überwachen und es als responsives Objekt bezeichnen. Wie geht das konkret? Funktion beobachten(Daten) { wenn (istObjekt(Daten)) { Objekt.Schlüssel(Daten).fürJeden(Schlüssel => { defineReactive(Daten, Schlüssel) }) } } Funktion defineReactive(Objekt, Eigenschaft) { let val = obj[Eigenschaft] let dep = new Dep() // Wird zum Sammeln von Abhängigkeiten verwendetObject.defineProperty(obj, prop, { erhalten() { // Der Zugriff auf Objekteigenschaften zeigt an, dass die aktuellen Objekteigenschaften abhängig sind und die Abhängigkeiten gesammelt werden dep.depend() Rückgabewert } setze(neuerWert) { wenn (neuerWert === Wert) zurückgeben // Die Daten wurden geändert. Es ist Zeit, das zuständige Personal zu benachrichtigen, damit die entsprechenden Ansichten aktualisiert werden. val = newVal dep.benachrichtigen() } }) // Umfassende Überwachung if (isObject(val)) { beobachten(Wert) } Rückgabeobjekt } Hier benötigen wir eine Dep-Klasse (Abhängigkeit), um die Abhängigkeitssammlung durchzuführen🎭 PS: Object.defineProperty kann nur vorhandene Eigenschaften überwachen, ist aber bei neu hinzugefügten Eigenschaften machtlos. Es kann auch keine Änderungen in Arrays überwachen (dieses Problem wird in Vue2 durch Umschreiben der Methode im Array-Prototyp gelöst), daher wird es in Vue3 durch einen leistungsstärkeren Proxy ersetzt. 2. So sammeln Sie Abhängigkeiten - implementieren Sie die Dep-KlasseBasierend auf der Konstruktorimplementierung: Funktion Dep() { // Verwenden Sie das Deps-Array, um verschiedene Abhängigkeiten zu speichern. this.deps = [] } // Dep.target wird verwendet, um die laufende Watcher-Instanz aufzuzeichnen, die ein global eindeutiger Watcher ist // Dies ist ein sehr cleveres Design, da JS ein Single-Thread ist und nur ein globaler Watcher gleichzeitig berechnet werden kann. Dep.target = null // Definieren Sie die Depend-Methode für den Prototyp, und jede Instanz kann darauf zugreifen Dep.prototype.depend = function() { wenn (Dep.target) { dies.deps.push(Dep.target) } } // Definieren Sie die Benachrichtigungsmethode für den Prototyp, um den Beobachter über die Aktualisierung zu informieren. Dep.prototype.notify = function() { dies.deps.forEach(watcher => { watcher.update() }) } // In Vue wird es verschachtelte Logik geben, z. B. Komponentenverschachtelung. Verwenden Sie daher den Stapel, um den verschachtelten Watcher aufzuzeichnen. // Stapel, zuerst rein, zuletzt raus const targetStack = [] Funktion pushTarget(_target) { wenn (Dep.target) ZielStack.push(Dep.target) Dep.Ziel = _Ziel } Funktion popTarget() { Dep.target = ZielStack.pop() } Hier verstehen wir hauptsächlich zwei Methoden des Prototyps: „depend“ und „notify“, eine zum Hinzufügen von Abhängigkeiten und die andere zum Benachrichtigen von Aktualisierungen. Wir sprechen über das Sammeln von „Abhängigkeiten“. Was genau wird also im Array this.deps gespeichert? Vue richtet das Konzept des Watchers zur Abhängigkeitsdarstellung ein, d. h., was in this.deps gesammelt wird, sind Watcher. 3. So aktualisieren Sie bei Datenänderungen - Implementierung der Watcher-KlasseEs gibt drei Arten von Watchern in Vue, die für die Seitendarstellung und die beiden APIs „Computed“ und „Watch“ verwendet werden. Zur Unterscheidung werden Watcher mit unterschiedlicher Verwendung jeweils als „RenderWatcher“, „ComputedWatcher“ und „WatchWatcher“ bezeichnet. Implementieren Sie es mit der Klasse: Klasse Watcher { Konstruktor(expOrFn) { // Wenn der hier übergebene Parameter keine Funktion ist, muss er analysiert werden, parsePath wird weggelassen this.getter = typeof expOrFn === 'function' ? expOrFn : parsePath(expOrFn) dies.get() } // Es ist nicht erforderlich, beim Definieren von Funktionen in der Klasse eine Funktion zu schreiben. erhalten() { // An diesem Punkt der Ausführung ist dies die aktuelle Watcher-Instanz und auch Dep.target drückeZiel(dieses) dieser.Wert = dieser.Getter() popZiel() } aktualisieren() { dies.get() } } An diesem Punkt hat ein einfaches reaktionsfähiges System Gestalt angenommen. Zusammengefasst: Object.defineProperty ermöglicht es uns zu wissen, wer auf die Daten zugreift und wann sich die Daten ändern, Dep kann aufzeichnen, welche DOMs mit bestimmten Daten verknüpft sind, und Watcher kann das DOM benachrichtigen, damit es aktualisiert wird, wenn sich die Daten ändern. 2. Virtueller DOM und Diff 1. Was ist ein virtueller DOM?Virtuelles DOM verwendet Objekte in JS, um das reale DOM darzustellen. Wenn sich Daten ändern, ändern Sie diese zuerst im virtuellen DOM und dann im realen DOM. Gute Idee! 💡 Bezüglich der Vorteile von virtuellem DOM ist es besser, auf Youda zu hören:
Zum Beispiel: <Vorlage> <div id="Anwendung" Klasse="Container"> <h1>HALLO WELT! </h1> </div> </Vorlage> // Entsprechender vnode { Tag: 'div', Requisiten: { ID: 'App', Klasse: 'Container' }, untergeordnete Elemente: { tag: ‚h1‘, untergeordnete Elemente: ‚HALLO WELT!‘ ' } } Wir können es wie folgt definieren: Funktion VNode(Tag, Daten, Kinder, Text, Ulme) { this.tag = Tag this.data = Daten this.childern = Kinder dieser.text = Text this.elm = elm // Verweis auf den realen Knoten} 2. Diff-Algorithmus - Vergleich zwischen neuen und alten KnotenWenn sich die Daten ändern, wird der Rendering Watcher-Callback ausgelöst und die Ansicht aktualisiert. Im Vue-Quellcode wird die Patch-Methode verwendet, um beim Aktualisieren der Ansicht die Ähnlichkeiten und Unterschiede zwischen neuen und alten Knoten zu vergleichen. (1) Bestimmen Sie, ob der neue und der alte Knoten dieselben Knoten sind Funktion sameVNode() Funktion sameVnode(a, b) { Rückgabewert a.Schlüssel === b.Schlüssel && ( a.tag === b.tag && a.istKommentar === b.istKommentar && isDef(a.data) === isDef(b.data) && gleicherEingabetyp(a, b) ) } (2) Wenn die neuen und alten Knoten unterschiedlich sind Alten Knoten ersetzen: neuen Knoten erstellen --> alten Knoten löschen (3) Wenn der neue und der alte Knoten gleich sind
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 // Das Obige sind die Kopf- und Endzeiger der neuen und alten Vnodes, die Kopf- und Endknoten der neuen und alten Vnodes while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { // Wenn die while-Bedingung nicht erfüllt ist, bedeutet dies, dass mindestens einer der alten und neuen Vnodes einmal durchlaufen wurde. Anschließend verlassen wir die Schleife, wenn (isUndef(oldStartVnode)) { oldStartVnode = oldCh[++oldStartIdx] // Vnode wurde nach links verschoben } sonst wenn (isUndef(oldEndVnode)) { oldEndVnode = alterCh[--oldEndIdx] } sonst wenn (gleicherVnode(alterStartVnode, neuerStartVnode)) { // Vergleichen Sie den alten Start und den neuen Start, um zu sehen, ob es sich um denselben Knoten handelt: patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) alterStartVnode = alterCh[++alteStartIdx] neuerStartVnode = neuerCh[++neueStartIdx] } sonst wenn (gleicherVnode(alterVnodeEndknoten, neuerVnodeEndknoten)) { // Vergleichen Sie die alten und neuen Enden, um zu sehen, ob es sich um denselben Knoten handelt: patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) oldEndVnode = alterCh[--oldEndIdx] neuerEndVnode = neuerCh[--newEndIdx] } sonst wenn (sameVnode(oldStartVnode, newEndVnode)) { // Vnode nach rechts verschoben // Vergleichen Sie den alten Anfang und das neue Ende, um zu sehen, ob es sich um denselben Knoten handelt: patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) 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 // Vergleichen Sie das alte Ende und den neuen Anfang, um zu sehen, ob es sich um denselben Knoten handelt: patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) kann verschieben und nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) oldEndVnode = alterCh[--oldEndIdx] neuerStartVnode = neuerCh[++neueStartIdx] } anders { // Der Unterschied zwischen dem Festlegen eines Schlüssels und dem Nichtfestlegen eines Schlüssels: // Ohne den Schlüssel zu setzen, vergleichen newCh und oldCh nur Kopf und Ende. Nach dem Setzen des Schlüssels wird zusätzlich zum Vergleich zwischen Kopf und Ende der passende Knoten aus dem vom Schlüssel generierten Objekt oldKeyToIdx gefunden. Daher kann das Setzen von Schlüsseln für Knoten DOM effizienter nutzen. wenn (isUndef(alterKeyToIdx)) alterKeyToIdx = createKeyToOldIdx(alterCh, alteStartIdx, alteEndIdx) idxInOld = isDef(neuerStartVnode.key) ? oldKeyToIdx[neuerStartVnode.key] : findIdxInOld(neuerStartVnode, alterCh, alteStartIdx, alteEndIdx) // Extrahiere die Knoten mit Schlüsseln aus der oldVnode-Sequenz und füge sie in die Karte ein, dann durchlaufe die neue vnode-Sequenz // Bestimmen Sie, ob der Schlüssel des vnode in der Karte ist. Wenn ja, suchen Sie den oldVnode, der dem Schlüssel entspricht. Wenn der oldVnode derselbe ist wie der durchlaufe vnode, verwenden Sie den Dom erneut und verschieben Sie die Dom-Knotenposition 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) 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(übergeordneteElm, alterCh, alteStartIdx, alteEndIdx) } } Die Hauptlogik hier ist: Vergleichen Sie den Kopf und das Ende des neuen Knotens mit dem Kopf und dem Ende des alten Knotens, um zu sehen, ob es sich um dieselben Knoten handelt. Wenn dies der Fall ist, patchen Sie Vnode direkt; andernfalls verwenden Sie eine Map, um die Schlüssel der alten Knoten zu speichern, und durchlaufen Sie dann die Schlüssel der neuen Knoten, um zu sehen, ob sie in den alten Knoten vorhanden sind. Wenn sie gleich sind, verwenden Sie sie erneut; die Zeitkomplexität ist hier O(n) und die Raumkomplexität ist ebenfalls O(n), wobei der Raum zum Tauschen von Zeit verwendet wird~ Der Diff-Algorithmus wird hauptsächlich verwendet, um die Anzahl der Aktualisierungen zu reduzieren, den DOM mit dem geringsten Unterschied zu finden und nur den Unterschied zu aktualisieren. 3. nächsterTickDer sogenannte nextTick bedeutet den nächsten Tick. Was also ist ein Tick? Wir wissen, dass die Ausführung von JS einfädig erfolgt. Es verarbeitet asynchrone Logik basierend auf der Ereignisschleife, die hauptsächlich in die folgenden Schritte unterteilt ist:
Der Ausführungsprozess des Hauptthreads ist ein Häkchen, und alle asynchronen Ergebnisse werden über die "Task-Warteschlange" geplant. In der Nachrichtenwarteschlange werden die Aufgaben einzeln gespeichert. Die Spezifikation legt fest, dass Aufgaben in zwei Kategorien unterteilt werden, nämlich Makroaufgaben und Mikroaufgaben, und dass alle Mikroaufgaben nach Abschluss jeder Makroaufgabe gelöscht werden müssen. für (MakroTask von MakroTaskQueue) { // 1. Aktuelle MACRO-TASK bearbeiten handleMacroTask() // 2. Erledigen Sie alle MICRO-TASK für (Mikrotask von Mikrotaskqueue) { Mikrotask handhaben (Mikrotask) } } Zu den gängigen Makroaufgaben in der Browserumgebung zählen „setTimeout“, „MessageChannel“, „postMessage“, „setImmediate“ und „setInterval“; zu den gängigen Mikroaufgaben zählen „MutationObsever“ und „Promise.then“. Wir wissen, dass das erneute Rendern des DOM aufgrund von Datenänderungen ein asynchroner Prozess ist, der im nächsten Tick erfolgt. Wenn wir beispielsweise während des Entwicklungsprozesses Daten von der Serverschnittstelle abrufen, werden die Daten geändert. Wenn einige unserer Methoden auf DOM-Änderungen angewiesen sind, nachdem die Daten geändert wurden, müssen wir sie nach nextTick ausführen. Beispielsweise der folgende Pseudocode: getData(res).then(() => { dies.xxx = res.data this.$nextTick(() => { // Hier können wir das geänderte DOM abrufen }) }) IV. FazitDies ist das Ende dieses Artikels über die Implementierung von Reaktionsfähigkeit beim Lernen von Vue-Quellcode. Weitere relevante Inhalte zur Implementierung von Reaktionsfähigkeit in Vue finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird! Das könnte Sie auch interessieren:
|
<<: Verwendung der MySQL SHOW STATUS-Anweisung
Inhaltsverzeichnis 1. Docker-Image 2. Erstellen S...
Zum Beispiel: Code kopieren Der Code lautet wie fo...
1. Voraussetzungen Da ich es schon mehrmals insta...
Verwenden Sie v-model, um das Paging-Informations...
Der spezifische Code zur Implementierung des einz...
Unter Linux ist alles eine Datei, daher besteht d...
In diesem Artikelbeispiel wird der spezifische Co...
Lassen Sie mich zunächst erklären, dass wir uns o...
Code: <input Typ="text" Klasse="...
Vorwort Dieser Artikel stellt hauptsächlich die r...
Bei der Arbeit an mobilen Seiten werden in letzte...
Voraussetzungen Um Container auf Windows Server a...
Effektbild: Implementierungscode: <Vorlage>...
Bei Datenbanken, die schon lange laufen, besteht ...
Vorwort Vor ein paar Tagen bin ich zufällig auf d...