js realisiert bidirektionale Datenbindung (Accessor-Überwachung)

js realisiert bidirektionale Datenbindung (Accessor-Überwachung)

In diesem Artikelbeispiel wird der spezifische Code von js zur Realisierung einer bidirektionalen Datenbindung zu Ihrer Information geteilt. Der spezifische Inhalt ist wie folgt

Zweiwegebindung:

Die bidirektionale Bindung basiert auf dem MVVM-Modell: model-view-viewModel

Modell: Modellschicht, verantwortlich für die Geschäftslogik und die Interaktion mit der Datenbank
Ansicht: Die Ansichtsebene ist dafür verantwortlich, das Datenmodell mit der Benutzeroberfläche zu kombinieren und auf der Seite anzuzeigen.
viewModel: Ansichtsmodellebene, die als Kommunikationsbrücke zwischen Modell und Ansicht dient

Die Bedeutung der bidirektionalen Bindung lautet: Wenn sich die Modelldaten ändern, wird die Ansichtsebene benachrichtigt. Wenn der Benutzer die Daten in der Ansichtsebene ändert, wird dies in der Modellebene berücksichtigt.

Der Vorteil der bidirektionalen Datenbindung besteht darin, dass sie sich nur auf Datenoperationen konzentriert und DOM-Operationen reduziert.

Das Prinzip der Vue.js-Implementierung besteht in der Verwendung einer Accessor-Überwachung. Daher wird auch hier eine Accessor-Überwachung verwendet, um eine einfache bidirektionale Datenbindung zu erreichen.

Die Implementierung der Accessor-Überwachung verwendet hauptsächlich die native Methode in JavaScript: Object.defineProperty. Diese Methode kann einem Objekt Accessor-Eigenschaften hinzufügen. Wenn auf die Objekteigenschaft zugegriffen wird oder ihr ein Wert zugewiesen wird, wird die Accessor-Eigenschaft ausgelöst. Daher kann mit dieser Idee ein Handler zur Accessor-Eigenschaft hinzugefügt werden.

Hier implementieren wir zunächst einen einfachen bidirektionalen Datenbindungsprozess für das Eingabe-Tag und verschaffen uns zunächst ein allgemeines Verständnis davon, was bidirektionale Datenbindung ist.

<Eingabetyp="Text">

<Skript>
// Holen Sie sich das Eingabefeldobjekt let input = document.querySelector('input');
// Erstellen Sie ein Objekt ohne Prototypkette, um Änderungen an einer Eigenschaft des Objekts zu überwachen. let model = Object.create(null);
// Wenn die Maus vom Eingabefeld weg bewegt wird, benachrichtigt die Ansichtsebene die Modellebene über Datenänderungen input.addEventListener('blur',function() {
    Modell['Benutzer'] = dieser.Wert;
})

// Wenn sich die Daten der Modellebene ändern, benachrichtigen Sie die Ansichtsebene über die Änderung.
Object.defineProperty(Modell, 'Benutzer', {
    Satz(v) {
        Benutzer = v;
        Eingabewert = v;
    },
    erhalten() {
        Benutzer zurückgeben;
    }
})
</Skript>

Im obigen Code wird zuerst das Eingabe-Tag-Objekt abgerufen und dann dem Eingabeelementobjekt ein Abhörereignis (Unschärfe) hinzugefügt. Wenn das Ereignis ausgelöst wird, d. h. wenn sich die Ansichtsebene ändert, muss die Modellebene benachrichtigt werden, damit die Daten aktualisiert werden. Die Modellebene verwendet hier ein leeres Objekt ohne Prototyp (der Grund für die Verwendung eines leeren Objekts besteht darin, ein Missverständnis der Daten beim Abrufen eines bestimmten Attributs aufgrund der Existenz der Prototypkette zu vermeiden).

Verwenden Sie die Methode Object.defineProperty, um Accessor-Eigenschaften für die angegebenen Eigenschaften des Objekts hinzuzufügen. Wenn die Eigenschaft des Objekts geändert wird, wird der Setter-Accessor ausgelöst. Hier können wir den Daten der Ansichtsebene Werte zuweisen und die Daten der Ansichtsebene aktualisieren. Die Ansichtsebene bezieht sich hier auf den Attributwert des Eingabe-Tags.

Schauen Sie sich die Wirkung an:

Geben Sie Daten in das Textfeld ein und drucken Sie model.user in der Konsole, um zu sehen, dass die Daten die Modellebene beeinflusst haben.

Ändern Sie dann die Daten der Modellebene manuell in der Konsole: model.user = '9090';
An dieser Stelle können Sie sehen, dass das Datentextfeld ebenfalls entsprechend geändert wurde, was sich auf die Ansichtsebene auswirkt

Nun, die einfachste bidirektionale Datenbindung für Textfelder ist implementiert. Aus dem obigen Fall können wir die folgende Implementierungslogik ableiten:

①. Um eine Datenkommunikation von der Ansichtsebene zum Modell zu erreichen, müssen Sie die Datenänderungen in der Ansichtsebene und den Wert der Ansichtsebene kennen. Im Allgemeinen müssen Sie jedoch den Wert des Tags selbst abrufen, es sei denn, es gibt ein integriertes Attribut, z. B. das Werteattribut des Eingabe-Tags, das den Eingabewert des Textfelds abrufen kann.

②. Verwenden Sie Object.defineProperty, um die Kommunikation von der Modellebene zur Ansichtsebene zu implementieren. Wenn die Daten geändert werden, wird der Accessor-Eigenschaftssetter sofort ausgelöst, sodass alle Ansichtsebenen, die die Eigenschaft verwenden, benachrichtigt werden können, um ihre aktuellen Daten zu aktualisieren (Beobachter).

③. Die gebundenen Daten müssen eine Eigenschaft eines Objekts sein, da Object.defineProperty eine für die Eigenschaften eines Objekts aktivierte Zugriffsfunktion ist.

Basierend auf der obigen Zusammenfassung können wir einen bidirektionalen Datenbindungsmodus ähnlich wie vue.js entwerfen:
Verwenden Sie benutzerdefinierte Anweisungen, um die Datenkommunikation von der Ansichtsebene zur Modellebene zu implementieren. Verwenden Sie Object.defineProperty, um die Datenkommunikation von der Modellebene zur Ansichtsebene zu implementieren.

Die Implementierung umfasst hierbei drei Hauptfunktionen:

  • _observer: verarbeitet die Daten und schreibt den Getter/Setter jedes Attributs neu
  • _compile: analysiert benutzerdefinierte Anweisungen (hier sind nur e-bind/e-click/e-model beteiligt), bindet native Verarbeitungsereignisse während des Analysevorgangs an Knoten und implementiert die Bindung von der Ansichtsebene zur Modellebene
  • Watcher: Als Zwischenbrücke zwischen Modell und Ansicht aktualisiert er die Ansichtsebene zusätzlich, wenn sich das Modell ändert.

Implementierungscode:

html

<!DOCTYPE html>
<html lang="de">
<Kopf>
    <meta charset="UTF-8">
    <title>Zweiseitige Datenbindung</title>
    <Stil>
        #app {
            Textausrichtung: zentriert;
        }
    </Stil>
    <script src="/js/eBind.js"></script>
    <Skript>
        fenster.onload = Funktion () {
           let ebind = neues EBind({
                el: '#app',
                Daten: {
                    Nummer: 0,
                    Person:
                        Alter: 0
                    }
                },
                Methoden: {
                    Inkrement: Funktion () {
                        diese.Nummer++;
                    },
                    Alter hinzufügen: Funktion () {
                        diese.Person.Alter++;
                    }
                }
            })
        }
    </Skript>
</Kopf>
<Text>
<div id="app">
    <form>
        <Eingabetyp="Text" e-Modell="Nummer">
        <button type="button" e-click="increment">Erhöhen</button>
    </form>
    <input e-model="Nummer" Typ="Text">
    <form>
        <input type="text" e-model="person.alter">
        <button type="button" e-click="addAge">Hinzufügen</button>
    </form>
    <h3 e-bind="Person.Alter"></h3>
</div>
</body>
</html>

eBind.js

Funktion EBind(Optionen) {
    this._init(Optionen);
}

// Initialisiere die bidirektionale Datenbindung entsprechend den angegebenen benutzerdefinierten Parametern EBind.prototype._init = function (options) {
    // Optionen sind die Daten für die Initialisierung, einschließlich el, Daten, Methode
    dies.$options = Optionen;

    // el ist das Elementobjekt, das verwaltet werden muss, el:#app this.$el:id ist das Elementobjekt der App this.$el = document.querySelector(options.el);

    // Daten dies.$data = options.data;

    //Methoden this.$methods = options.methods;

    // _binding speichert die Zuordnung zwischen Modell und Ansicht, also der Wachter-Instanz. Wenn das Modell aktualisiert wird, wird die entsprechende Ansicht aktualisiert
    diese._bindung = {};

    // Überschreibe die Get- und Set-Methoden von this.$data this._obverse(this.$data);

    // Anweisungen analysieren this._compile(this.$el);
}


// Die Funktion besteht darin, alle Attribute in this.$data zu überwachen, den Zugriff zu überwachen und die Datenkommunikation vom Modell zur Ansichtsebene zu realisieren. Wenn sich die Modellebene ändert, benachrichtigen Sie die Ansichtsebene EBind.prototype._obverse = function (currentObj, completeKey) {
    // Kontext speichern var _this = this;

    // currentObj ist das Objekt, dessen get/set neu geschrieben werden muss. Object.keys ruft die Eigenschaften des Objekts ab und erhält ein Array. // Das Array durchlaufen Object.keys(currentObj).forEach(function (key) {

        // Genau dann, wenn die eigenen Eigenschaften des Objekts überwacht werden if (currentObj.hasOwnProperty(key)) {

            // Wenn es eine Eigenschaft eines Objekts ist, muss es in der Form „person.age“ gespeichert werden. var completeTempKey = completeKey? completeKey + '.' + key : key;

            // Stelle die Verbindung der zu überwachenden Attribute her_this._binding[completeTempKey] = {
                _directives: [] // Speichert alle Stellen, an denen diese Daten verwendet werden};

            // Den Wert des aktuellen Attributs abrufen var value = currentObj[key];

            // Wenn der Wert ein Objekt ist, iteriere durch ihn und überwache jedes Objektattribut vollständig, wenn (Typ des Wertes == 'Objekt') {
                _this._obverse(Wert, kompletterTempKey);
            }

            var Bindung = _this._binding[completeTempKey];

            // Ändere das Abrufen und Festlegen jeder Eigenschaft des Objekts und füge Verarbeitungsereignisse in Abrufen und Festlegen hinzu. Object.defineProperty(currentObj, key, {
                aufzählbar: wahr,
                konfigurierbar: true, // Standardmäßig nicht auf false setzen
                erhalten() {
                    Rückgabewert;
                },
                Satz(v) {
                    // value speichert den Wert des aktuellen Attributs if (value != v) {
                        // Wenn die Daten geändert werden, muss jede Stelle, die die Daten verwendet, benachrichtigt werden, um die Daten zu aktualisieren. Das heißt, das Modell benachrichtigt die Ansichtsebene, und die Watcher-Klasse fungiert als Zwischenebene, um den Vorgang abzuschließen (Benachrichtigungsvorgang).
                        Wert = v;
                        binding._directives.forEach(Funktion (Element) {
                            Element.update();
                        })
                    }
                }
            })
        }
    })
}


// Die Funktion besteht darin, benutzerdefinierte Anweisungen zu kompilieren, ihnen native Abhörereignisse hinzuzufügen und die Datenkommunikation von der Ansichts- zur Modellebene zu realisieren, d. h., die Datenaktualisierung der Modellebene zu benachrichtigen, wenn sich die Daten der Ansichtsebene ändern // Implementierungsprinzip: Alle untergeordneten Knoten über das verwaltete Elementobjekt abrufen: this.$el, alle untergeordneten Knoten durchlaufen, prüfen, ob sie benutzerdefinierte Attribute haben und ob sie benutzerdefinierte Attribute mit angegebenen Bedeutungen haben // Beispiel: e-bind/e-model/e-click fügt Abhörereignisse entsprechend den verschiedenen dem Knoten hinzugefügten benutzerdefinierten Attributen hinzu // e-click fügt natives Onclick-Ereignis hinzu. Der wichtigste Punkt hierbei ist: Der Kontext this der in this.$method angegebenen Methode muss in this.$data geändert werden.
// e-Modell ist an Datenaktualisierung gebunden, hier werden nur Eingabe- und Textbereichs-Tags unterstützt, Grund: Die Datenkommunikation von der Ansichts- zur Modellebene wird durch die Verwendung des Werteattributs des Tags selbst realisiert // e-Bind
EBind.prototype._compile = Funktion (Stamm) {
    // Ausführungskontext speichern var _this = this;

    // Alle untergeordneten Knoten des verwalteten Knotenelements abrufen, einschließlich nur Elementknoten var nodes = root.children;

    für (lass i = 0; i < Knotenlänge; i++) {
        // Die untergeordneten Knoten in der richtigen Reihenfolge abrufen var node = nodes[i];

        // Wenn der aktuelle Knoten untergeordnete Knoten hat, verarbeite die untergeordneten Knoten weiter Schicht für Schicht, wenn (node.children.length) {
            dies._kompilieren(Knoten);
        }

        // Wenn der aktuelle Knoten an das E-Click-Attribut gebunden ist, müssen Sie das Onclick-Ereignis an den aktuellen Knoten binden, wenn (node.hasAttribute('e-click')) {
            // hasAttribute kann benutzerdefinierte Attribute abrufennode.addEventListener('click',(function () {
                // Den Attributwert des aktuellen Knotens abrufen, d. h. die Methode var attrVal = node.getAttribute('e-click');
                // Da die Daten in der gebundenen Methode die Daten in Daten verwenden müssen, muss der Kontext der ausgeführten Funktion, also dieser, in this.$data geändert werden.
                // Der Grund für die Verwendung von „Bind“ anstelle von „Call/Apply“ besteht darin, dass die Onclick-Methode vor ihrer Ausführung ausgelöst werden muss, anstatt sie sofort auszuführen. return _this.$methods[attrVal].bind(_this.$data);
            })())
        }


        // Die bidirektionale Bindung kann nur auf Eingabe- und Textbereichs-Tagelemente angewendet werden, da das integrierte Wertattribut dieser beiden Tags zum Implementieren der bidirektionalen Bindung verwendet wird, wenn (node.hasAttribute('e-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {
            // Fügen Sie dem Elementobjekt einen Input-Event-Listener hinzu. Der zweite Parameter ist eine sofort ausgeführte Funktion. Sie ruft den Indexwert des Knotens ab, führt den Code innerhalb der Funktion aus und gibt den Event-Handler node.addEventListener('input', (function (index) { zurück.
                // Den Attributwert des aktuellen Knotens abrufen, d. h. die Methode var attrVal = node.getAttribute('e-model');

                // Fügen Sie eine Zuordnung des Modells zur Ansichtsebene für das aktuelle Element hinzu object_this._binding[attrVal]._directives.push(new Watcher({
                    Name: "Eingabe",
                    el: Knoten,
                    eb: _dies,
                    exp: attrVal,
                    attr: 'Wert'
                }))

                // Wenn sich der Wert des Eingabe-Tags ändert, müssen die Daten der Modellebene aktualisiert werden, d. h. der Wechsel von der Ansichtsebene zur Modellebene return function () {
                    // Holen Sie sich das gebundene Attribut, wobei Sie . als Trennzeichen verwenden. Wenn es sich nur um einen Wert handelt, holen Sie sich direkt den aktuellen Wert. Wenn es sich um ein Objekt (obj.key) handelt, ist die Bindung tatsächlich der Wert des Schlüssels im obj-Objekt. Zu diesem Zeitpunkt müssen Sie den Schlüssel abrufen und ihn dem Wert des geänderten Eingabetags zuweisen var keys = attrVal.split('.');

                    // Holen Sie sich das letzte Attribut im Satz von Attributen, die Sie im vorigen Schritt erhalten haben (das letzte Attribut ist der tatsächliche gebundene Wert)
                    var letzterSchlüssel = Schlüssel[Schlüssel.Länge - 1];

                    // Holen Sie sich das übergeordnete Objekt des tatsächlich gebundenen Wertes // Denn wenn es sich um ein Objekt wie z. B. obj.key.val handelt, müssen Sie die Referenz des Schlüssels finden, da das, was Sie hier ändern möchten, val ist
                    // Ändere den Wert von val, indem du auf key referenzierst. Wenn du jedoch direkt auf val referenzierst, ist val ein numerischer Speicher. Wenn du ihn einer anderen Variablen zuweist, öffnet sich tatsächlich ein neuer Speicherplatz. // Du kannst die Daten in der Modellebene, also this.$data, nicht direkt ändern. Wenn du auf den Datenspeicher referenzierst, weise ihn einer anderen Variablen zu, und die Änderung der anderen Variablen wirkt sich auf die ursprünglich referenzierten Daten aus. // Hier musst du also das übergeordnete Objekt des tatsächlich gebundenen Werts finden, also den obj-Wert in obj.key var model = keys.reduce(function (value, key) {
                        // Wenn es kein Objekt ist, gib den Attributwert direkt zurück
                        wenn (Typ des Wertes[Schlüssel] !== 'Objekt') {
                            Rückgabewert;
                        }

                        Rückgabewert[Schlüssel];
                        // Hier verwenden wir die Modellebene als Startwert, da Schlüssel die Attribute in this.$data aufzeichnen. Daher müssen wir das Zielattribut vom übergeordneten Objekt this.$data finden}, _this.$data);

                    // Modell ist das zuvor erwähnte übergeordnete Objekt, das Objekt in obj.key und lastkey ist das tatsächlich gebundene Attribut. Sobald es gefunden wurde, muss es auf den Wert des Knotens aktualisiert werden.
                    // Die Änderung der Modellebene hier löst den Accessor-Eigenschafts-Setter in _observe aus. Wenn diese Eigenschaft also woanders verwendet wird, wird sie sich auch entsprechend ändern. model[lastKey] = nodes[index].value;
                }
            })(ich))
        }


        // Binden Sie e-bind an den Knoten und fügen Sie eine Zuordnung vom Modell zur Ansicht hinzu. Der Grund dafür ist, dass e-bind die Datenkommunikation vom Modell zur Ansicht implementiert und in this._observer // über definePrototype implementiert wurde. Daher müssen Sie hier nur die Kommunikation hinzufügen, um die Implementierung in _oberver zu erleichtern.
        wenn(node.hasAttribute('e-bind')) {
            var attrVal = node.getAttribute('e-bind');
            _this._binding[attrVal]._directives.push(neuer Watcher({
                Name: 'Text',
                el: Knoten,
                eb: _dies,
                exp: attrVal,
                attr: 'innerHTML'
            }))
        }
    }
}

/**
 * Optionseigenschaft:
 * Name: Knotenname: Textknoten: Text, Eingabefeld: Eingabe
 * el: DOM-Element, das der Anweisung entspricht* eb: EBind-Instanz, die der Anweisung entspricht* exp: Wert, der der Anweisung entspricht: e-bind="test"; Test ist der Wert, der der Anweisung entspricht* attr: Wert des gebundenen Attributs, zum Beispiel: Das durch e-bind gebundene Attribut wird tatsächlich in innerHTML wiedergegeben, und das durch v-model gebundene Tag wird im Wert wiedergegeben*/
Funktion Watcher(Optionen) {
    dies.$options = Optionen;
    dies.update();
}

Watcher.prototype.update = Funktion () {
    // Kontext speichern var _this = this;
    // Holen Sie sich das gebundene Objekt var keys = this.$options.exp.split('.');

    // Das zu ändernde Attribut im DOM-Objekt abrufen und ändern this.$options.el[this.$options.attr] = keys.reduce(function (value, key) {
        Rückgabewert[Schlüssel];
    }, _this.$options.eb.$data)
}

Ergebnis:

Das Obige ist der vollständige Inhalt dieses Artikels. Ich hoffe, er wird für jedermanns Studium hilfreich sein. Ich hoffe auch, dass jeder 123WORDPRESS.COM unterstützen wird.

Das könnte Sie auch interessieren:
  • Super detaillierte grundlegende JavaScript-Syntaxregeln
  • Tutorial zur grundlegenden Syntax von js und zur Konfiguration von Maven-Projekten
  • Detaillierte Erklärung der Destrukturierungszuweisungssyntax in Javascript
  • Einige Datenverarbeitungsmethoden, die häufig in JS verwendet werden können
  • js realisiert das dynamische Laden von Daten durch Wasserfallfluss
  • Detaillierte Erklärung der grundlegenden Syntax und Datentypen von JavaScript

<<:  Alibaba Cloud beantragt ein kostenloses SSL-Zertifikat (https) von Cloud Shield

>>:  Beispiel für das Abrufen der neuesten Daten mithilfe einer MySQL-Mehrtabellenassoziations-Eins-zu-viele-Abfrage

Artikel empfehlen

MySQL vollständig deinstallieren. Persönlicher Test!

MySQL sauber deinstallieren. Persönlich getestet,...

So kopieren Sie schnell große Dateien unter Linux

Daten kopieren Beim Remote-Kopieren von Daten ver...

Detaillierte Erklärung des Unterschieds zwischen CSS-Link und @import

Wie fügt man CSS in HTML ein? Es gibt drei Möglic...

Zwei Möglichkeiten zum Löschen von Tabellendaten in MySQL und ihre Unterschiede

Es gibt zwei Möglichkeiten, Daten in MySQL zu lös...

Implementierungsbeispiel für die Message Board-Funktion von Node.js+Express

Inhaltsverzeichnis Nachrichtenbrett Erforderliche...

Implementierung der TCPWrappers-Zugriffskontrolle in Centos

1. Übersicht über TCP-Wrapper TCP Wrapper „verpac...

Tabellen-Paging-Funktion implementiert durch Vue2.0+ElementUI+PageHelper

Vorwort Ich habe kürzlich an einigen Front-End-Pr...

Beispiel für den Aufbau eines Jenkins-Dienstes mit Docker

Ziehen Sie das Bild root@EricZhou-MateBookProX: D...

Serviceverwaltung der Quellpaketinstallation unter Linux

Inhaltsverzeichnis 1. Startverwaltung des Quellpa...

Beispiele für JavaScript-Entschüttelungen und Drosselung

Inhaltsverzeichnis Stabilisierung Drosselung: Ant...