Zum Hauptinhalt springen




En este capítulo, explicaré cómo crear una biblioteca de Verknüpfung de datos simple pero potente con los nuevos proxy de ES6.

Voraussetzungen

ES6 hizo que JavaScript fuera mucho más elegante, pero la mayoría de las nuevas características son sólo sintácticas. Los proxies son una de las pocas adiciones no rellenables. Si no está familiarizado con ellos, por favor, eche un vistazo a los documentos del MDN Proxy antes de continuar.

También será útil tener un conocimiento básico de los objetos ES6 Reflection API y Set, Map y WeakMap.

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.



function get (target, key, receiver) {
const result = Reflect.get(target, key, receiver)
if (currentObserver) {
registerObserver(target, key, currentObserver)
if (typeof result === 'object') {
const observableResult = observable(result)
Reflect.set(target, key, observableResult, receiver)
return observableResult
}
}
return result
}


La captura get anterior envuelve el valor devuelto en un proxy observable antes de devolverlo, en caso de que Sein un objeto. Esto también es perfecto desde el punto de vista del rendimiento, ya que los observables solo se crean cuando realmente los necesita un observador.