Zum Hauptinhalt springen




In diesem Kapitel werde ich erklären, wie Sie mit den neuen ES6-Proxys eine einfache, aber leistungsstarke Datenlinkbibliothek erstellen.

Voraussetzungen

ES6 hat JavaScript viel eleganter gemacht, aber die meisten neuen Funktionen sind nur syntaktisch. Proxies sind eine der wenigen nicht nachfüllbaren Ergänzungen. Wenn Sie mit ihnen nicht vertraut sind, lesen Sie bitte die MDN-Proxy-Dokumente, bevor Sie fortfahren.

Ein grundlegendes Verständnis der ES6-Reflection-API sowie der Set-, Map- und WeakMap-Objekte ist ebenfalls hilfreich.

Die nx-Observ-Bibliothek

nx-watch ist eine Datenbindungslösung in weniger als 140 Codezeilen. Es macht die beobachtbaren (obj) und beobachtenden (fn) Funktionen verfügbar, mit denen beobachtbare Objekte und Beobachterfunktionen erstellt werden. Eine Beobachterfunktion wird automatisch ausgeführt, wenn sich eine von ihr verwendete beobachtbare Eigenschaft ändert. Das folgende Beispiel zeigt dies.

// Dies ist ein beobachtbares Beispiel const person = beobachtbar ({Name: 'John', Alter: 20}) Funktion print () {console.log (`$ {person.name}, $ {person.age}`)} // dies erstellt eine Beobachterfunktion // gibt 'John, 20' an den Konsolenbeobachter aus (print) // Konsolenausgabe 'Dave, 20' setTimeout (() => person.name = 'Dave', 100) // Konsolenausgabe 'Dave, 22' setTimeout (() => person.age = 22, 200)

Die an watch () übergebene Druckfunktion wird jedes Mal erneut ausgeführt, wenn sich person.name oder person.age ändert. Print ruft erneut eine Beobachterfunktion auf.

Implementierung eines einfachen Observable.

In diesem Abschnitt werde ich erklären, was unter der Haube von nx-compare vor sich geht. Zunächst zeige ich Ihnen, wie Änderungen in den Eigenschaften eines Observablen erkannt und mit Beobachtern kombiniert werden. Anschließend erkläre ich eine Möglichkeit, die durch diese Änderungen ausgelösten Beobachterfunktionen auszuführen.

Änderungsprotokoll

Änderungen werden aufgezeichnet, indem beobachtbare Objekte in ES6-Proxys eingeschlossen werden. Diese Proxys fangen mithilfe der Reflection-API nahtlos Abruf- und Festlegungsvorgänge ab.

Variablen currentObserver und queueObserver () werden im folgenden Code verwendet, werden jedoch nur im nächsten Abschnitt erläutert. Im Moment reicht es zu wissen, dass currentObserver immer auf die aktuell ausgeführte Beobachterfunktion verweist, und queueObserver () ist eine Funktion, die einen Beobachter in die Warteschlange stellt, damit er bald ausgeführt werden kann.

/ * ordnet die beobachtbaren Eigenschaften einer Reihe von Beobachterfunktionen zu, indem die Eigenschaft * / const Observer = new WeakMap () / * auf die aktuell ausgeführte Beobachterfunktion verweist. Sie kann undefiniert sein * / let currentObserver / * erstellt ein Objekt Durch das Umschließen in einen Proxy kann eine leere Karte für die später zu speichernden Eigenschafts-Beobachter-Paare hinzugefügt werden. * / function Observable (obj) {Observer.set (obj, new Map ()) gibt neuen Proxy zurück (obj, {get, set})} / * Diese Falle fängt Operationen ab und führt nichts aus, wenn in diesem Moment kein Beobachter ausgeführt wird. * / function get (Ziel, Schlüssel, Empfänger) {const result = Reflect.get (Ziel, Schlüssel, Empfänger) if (currentObserver) {registerObserver (Ziel, Schlüssel, currentObserver)} return result} / * wenn eine Funktion As ausführt Als Beobachter koppelt diese Funktion die Beobachterfunktion mit der aktuell abgerufenen beobachtbaren Eigenschaft und speichert sie in der Beobachterkarte. * / function registerObserver (Ziel, Schlüssel, Beobachter) {let ObserverForKey = Observer.get (Ziel) .get (Schlüssel) if (! ObserverForKey) {ObserverForKey = New Set () Observer.get (Ziel) .set (Key, ObserverForKey )} ObserverForKey.add (Observer)} / * Dieser Trap fängt Set-Operationen ab und stellt jeden Beobachter in eine Warteschlange, der der aktuell festgelegten Eigenschaft zugeordnet ist, die später ausgeführt werden soll (Ziel) .get (Schlüssel) if (ObserverForKey) {ObserverForKey.forEach (QueueObserver)} Rückgabe Reflect.set (Ziel, Schlüssel, Wert, Empfänger)}

Die gett-Rampe macht nichts, wenn currentObserver nicht gesetzt ist. Andernfalls werden die erhaltenen beobachtbaren Eigenschaftsbeobachter und der aktuell ausgeführte Beobachter abgeglichen und auf der schwachen Karte gespeichert. Beobachter werden in einer beobachtbaren Set-Eigenschaft gespeichert. Dies stellt sicher, dass keine Duplikate vorhanden sind.

Die Einstellrampe dient dazu, alle Beobachter abzurufen, die mit der modifizierten beobachtbaren Eigenschaft gepaart sind, und sie für die spätere Ausführung in die Warteschlange zu stellen.

Im Folgenden finden Sie eine Abbildung und eine schrittweise Beschreibung des Beispielcodes von nx-compare.

JavaScript-Datenbindung mit es6-Proxy - beobachtbares Codebeispiel

  • Das von der Person beobachtbare Objekt wird erstellt.
  • currentObserver ist auf Druck eingestellt.
  • Der Druckvorgang beginnt.
  • person.name wird im Druck abgerufen.
  • Der Proxy Get Capture wird persönlich aufgerufen
  • Die Gruppe der Beobachter, die zum Paar (Person, Name) gehören, ist
  • abgerufen von Observer.get (Person) .get ('Name').
  • currentObserver (print) wird der Gruppe der Beobachter hinzugefügt.
  • Schritt 4-7 erneut mit person.age ausführen.
  • $ {person.name}, $ {person.age} Auf der Konsole gedruckt.
  • Der Druckvorgang wird ausgeführt.
  • currentObserver ist auf undefiniert gesetzt.
  • Ein anderer Code wird ausgeführt.
  • person.age wird auf einen neuen Wert gesetzt (22).
  • Die Proxy-Set-Erfassung wird persönlich aufgerufen
  • Die Gruppe der Beobachter, die zum Paar (Person, Alter) gehören, ist
  • abgerufen von Observer.get (Person) .get ('Alter').
  • Die Beobachter im Beobachter-Set (einschließlich Druck)
  • Sie werden zur Ausführung in die Warteschlange gestellt.
  • Der Druck läuft erneut.

Laufende Beobachter

Beobachter in der Warteschlange werden asynchron in einem Stapel ausgeführt, was zu einer überlegenen Leistung führt. Während der Registrierung werden Beobachter synchron zu Set queuedObservers hinzugefügt. Set kann keine Duplikate enthalten, sodass das mehrfache Einreihen desselben Beobachters nicht zu mehreren Ausführungen führt. Wenn Set zuvor leer war, ist eine neue Aufgabe geplant, um alle Beobachter in der Warteschlange nach einiger Zeit zu iterieren und auszuführen.

/ * enthält die aktivierten Beobachterfunktionen, die bald ausgeführt werden sollten * / const queuedObservers = new Set () / * zeigt auf den aktuellen Beobachter, kann undefiniert sein * / lass currentObserver / * die exponierte Beobachtungsfunktion * / function watch (fn) { queueObserver (fn)} / * fügt den Beobachter der Warteschlange hinzu und stellt sicher, dass die Warteschlange bald ausgeführt wird * / function queueObserver (Beobachter) {if (queuedObservers.size === 0) {Promise.resolve (). then (runObservers)} queuedObservers.add (Beobachter)} / * führt Beobachter in der Warteschlange aus, currentObserver wird am Ende auf undefiniert gesetzt * / function runObservers () {try {queuedObservers.forEach (runObserver)} endlich {currentObserver = undefiniert queuedObservers.clear ()} / * setzt den globalen currentObserver auf Observer und führt ihn dann aus * / function runObserver (Observer) {currentObserver = Observer Observer ()}

Der obige Code stellt sicher, dass jedes Mal, wenn ein Beobachter ausgeführt wird, die globale Variable currentObserver darauf verweist. Durch Aktivieren von currentObserver werden die gett-Rampen aktiviert, um currentObserver abzuhören und mit allen beobachtbaren Eigenschaften abzugleichen, die während der Ausführung verwendet werden.

Erstellen eines dynamischen beobachtbaren Baums

Bisher funktioniert unser Modell gut mit einstufigen Datenstrukturen, zwingt uns jedoch, jede neue objektwertige Eigenschaft in eine von Hand beobachtbare Eigenschaft zu verpacken. Beispielsweise würde der folgende Code nicht wie erwartet funktionieren.

const person = beobachtbar ({Daten: {Name: 'John'}}) Funktion print () {console.log (person.data.name)} // Konsolenausgabe 'John' beobachten (drucken) // macht nichts setTimeout ( () => person.data.name = 'Dave', 100)

Damit dieser Code funktioniert, müssten wir Observable ({Daten: {Name: 'John'}}) durch Observable ({Daten: Observable ({Name: 'John'})}) ersetzen. Glücklicherweise können wir diesen Nachteil beseitigen, indem wir ein get modifizieren.

Funktion get (Ziel, Schlüssel, Empfänger) {const result = Reflect.get (Ziel, Schlüssel, Empfänger) if (currentObserver) {registerObserver (Ziel, Schlüssel, currentObserver) if (Typ des Ergebnisses === 'Objekt') {const ObservableResult = Observable (Ergebnis) Reflect.set (Ziel, Schlüssel, ObservableResult, Empfänger) Rückgabe ObservableResult}} Rückgabe Ergebnis}

Das obige Get Capture verpackt den zurückgegebenen Wert in einen beobachtbaren Proxy, bevor es zurückgegeben wird, falls es sich um ein Objekt handelt. Dies ist auch unter Performance-Gesichtspunkten perfekt, da Observables nur dann erstellt werden, wenn sie tatsächlich von einem Beobachter benötigt werden.

R Marketing Digital