Implementieren Sie ein einfaches Front-End-MVVM-Framework ähnlich wie VUE

In diesem Blog werden wir natives JavaScript verwenden, um ein einfaches Front-End-MVVM-Framework zu implementieren, ähnlich wie VUE. MVVM, kurz für Model-View-ViewModel, ist ein Architekturmuster zum Erstellen moderner, wartbarer Front-End-Anwendungen. Das MVVM-Framework realisiert die bidirektionale Bindung zwischen Ansicht und Daten durch Datenbindung und Komponentisierung, sodass Datenänderungen automatisch in der Ansicht widergespiegelt werden können und gleichzeitig Änderungen in der Ansicht die Daten automatisch aktualisieren, wodurch das realisiert wird Synchronisierung von Daten und Ansichten erneuern.

Dieser Blog wird in mehrere Teile unterteilt, um den Prozess der Implementierung des MVVM-Frameworks vorzustellen. Zunächst stellen wir die Grundprinzipien und Kernkonzepte des MVVM-Frameworks vor. Anschließend werden wir nach und nach jedes Funktionsmodul des MVVM-Frameworks implementieren, einschließlich Datenhijacking, Kompilierungsvorlagen, Beobachter und Abhängigkeitserfassung. Abschließend demonstrieren wir die Verwendung des MVVM-Frameworks anhand eines einfachen Beispiels.

Bild

1. Grundprinzipien und Kernkonzepte des MVVM-Frameworks

Das MVVM-Framework ist ein datengesteuertes Front-End-Framework. Zu seinen Kernkonzepten gehören:

  • Modell : Stellt die Daten und Geschäftslogik der Anwendung dar. Im MVVM-Framework ist Model normalerweise ein JavaScript-Objekt, das zum Speichern von Anwendungsdaten verwendet wird.

  • Ansicht (Ansicht) : Stellt die Benutzeroberfläche dar. Im MVVM-Framework ist View normalerweise eine HTML-Vorlage zum Anzeigen von Daten.

  • ViewModel (Ansichtsmodell) : ist die Verbindungsschicht zwischen Ansicht und Modell. ViewModel ist dafür verantwortlich, die Daten des Modells in Daten umzuwandeln, die von der Ansicht angezeigt werden können, Ereignisse in der Ansicht abzuhören und die Daten im Modell zu aktualisieren, wenn sich die Ansicht ändert.

Das MVVM-Framework realisiert die bidirektionale Bindung zwischen Ansicht und Modell durch Datenbindung und Komponentisierung. Wenn sich die Daten im Modell ändern, wird die Ansicht automatisch aktualisiert; wenn sich die Daten in der Ansicht ändern, wird das Modell automatisch aktualisiert. Dieser bidirektionale Bindungsmechanismus hält Daten und Ansichten jederzeit synchron, was die Komplexität der Front-End-Entwicklung erheblich vereinfacht.

2. Implementieren Sie Observer: Datendiebstahl

 Datenhijacking ist eine der Kernfunktionen des MVVM-Frameworks, das Daten überwacht, indem es den Zugriff auf Objektattribute abfängt. In unserem MVVM-Framework werden wir ObserverKlassen verwenden, um die Datenhijacking-Funktionalität zu implementieren.

// observer.js

// 定义Dep类,用于收集依赖和通知更新
class Dep {
  constructor() {
    this.subs = {};
  }

  addSub(target) {
    this.subs[target.uid] = target;
  }

  notify() {
    for (let uid in this.subs) {
      this.subs[uid].update();
    }
  }
}

// 定义Observer类,用于实现数据劫持
export default class Observer {
  constructor(data) {
    this.data = data;
    this.walk(this.data);
  }

  walk(data) {
    if (!data || typeof data !== "object") {
      return;
    }
    Object.keys(data).forEach((key) => {
      this.defineReactive(data, key, data[key]);
    });
  }

  defineReactive(data, key, value) {
    var dep = new Dep();
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: false,
      get: () => {
        Dep.target && dep.addSub(Dep.target);
        return value;
      },
      set: (newValue) => {
        value = newValue;
        dep.notify();
      },
    });
    this.walk(value);
  }
}

Im obigen Code haben wir DepKlassen zum Sammeln von Abhängigkeiten und Benachrichtigen von Aktualisierungen sowie ObserverKlassen zum Implementieren von Daten-Hijacking-Funktionen definiert. DepDas Attribut in der Klasse subswird zum Speichern aller WatcherInstanzen verwendet, es wird getin der Methode zum Sammeln von Abhängigkeiten verwendet und notifyes wird in der Methode zum Benachrichtigen der Aktualisierung verwendet. ObserverDer Konstruktor der Klasse akzeptiert einen dataParameter, der das zu kapernde Datenobjekt angibt. walkDie Methode wird verwendet, um die Objektdaten zu durchlaufen und defineReactivedie Methode aufzurufen, um jede Eigenschaft zu kapern.

In defineReactiveder Methode definieren wir Object.definePropertydie Eigenschaften des Objekts und fangen den Zugriff und die Änderung der Eigenschaften ab. In getder Methode fügen wir Dep.target(die aktuelle WatcherInstanz) zu den entsprechenden Abhängigkeiten hinzu, sodass wir Aktualisierungen benachrichtigen können, wenn sich die Eigenschaften ändern. In der setMethode benachrichtigen wir alle zu aktualisierenden Abhängigkeiten, wenn sich die Eigenschaften ändern.

3. Implementieren Sie den Compiler: Vorlagenkompilierung

Die Vorlagenkompilierung ist eine weitere wichtige Funktion des MVVM-Frameworks, das Ansichten durch Parsen spezieller Symbole in Vorlagen (wie { {}}, V-Modell, V-Text usw.) aktualisiert . In unserem MVVM-Framework werden wir CompilerKlassen verwenden, um die Funktionalität zur Vorlagenkompilierung zu implementieren.

// compiler.js

import Watcher from "./watcher";

export default class Compiler {
  constructor(context) {
    this.$el = context.$el;
    this.context = context;
    if (this.$el) {
      this.$fragment = this.nodeToFragment(this.$el);
      this.compiler(this.$fragment);
      this.$el.appendChild(this.$fragment);
    }
  }

  nodeToFragment(node) {
    let fragment = document.createDocumentFragment();
    if (node.childNodes && node.childNodes.length) {
      node.childNodes.forEach((child) => {
        if (!this.ignorable(child)) {
          fragment.appendChild(child);
        }
      });
    }
    return fragment;
  }

  ignorable(node) {
    var reg = /^[\t\n\r]+/;
    return (
      node.nodeType === 8 || (node.nodeType === 3 && reg.test(node.textContent))
    );
  }

  compiler(fragment) {
    if (fragment.childNodes && fragment.childNodes.length) {
      fragment.childNodes.forEach((child) => {
        if (child.nodeType === 1) {
          this.compilerElementNode(child);
        } else if (child.nodeType === 3) {
          this.compilerTextNode(child);
        }
      });
    }
  }

  compilerElementNode(node) {
    let attrs = [...node.attributes];
    attrs.forEach((attr) => {
      let { name: attrName, value: attrValue } = attr;
      if (attrName.indexOf("v-") === 0) {
        let dirName = attrName.slice(2);
        switch (dirName) {
          case "text":
            new Watcher(attrValue, this.context, (newValue) => {
              node.textContent = newValue;
            });
            break;
          case "model":
            new Watcher(attrValue, this.context, (newValue) => {
              node.value = newValue;
            });
            node.addEventListener("input", (e) => {
              this.context[attrValue] = e.target.value;
            });
            break;
        }
      }
    });
    this.compiler(node);
  }

  compilerTextNode(node) {
    let text = node.textContent.trim();
    if (text) {
      let exp = this.parseTextExp(text);
      new Watcher(exp, this.context, (newValue) => {
        node.textContent = newValue;
      });
    }
  }

  parseTextExp(text) {
    let regText = /\{\{(.+?)\}\}/g;
    var pices = text.split(regText);
    var matches = text.match(regText);
    let tokens = [];
    pices.forEach((item) => {
      if (matches && matches.indexOf("{
   
   {" + item + "}}") > -1) {
        tokens.push("(" + item + ")");
      } else {
        tokens.push("`" + item + "`");
      }
    });
    return tokens.join("+");
  }
}

Im obigen Code definieren wir Compilereine Klasse zur Implementierung der Vorlagenkompilierungsfunktion. CompilerDer Konstruktor der Klasse akzeptiert einen contextParameter, der das Instanzobjekt des MVVM-Frameworks angibt. Im Konstruktor konvertieren wir das Stammelement des MVVM-Frameworks $elin ein Dokumentfragment und rufen compilereine Methode zum Kompilieren der Vorlage auf. Während des Kompilierungsprozesses analysieren wir jeden Elementknoten und Textknoten, identifizieren spezielle Symbole (z. B. V-Modell und V-Text) und erstellen entsprechende WatcherInstanzen, um eine reaktionsfähige Aktualisierung der Daten zu implementieren.

4. Implementieren Sie Watcher und Dep: Observer und Abhängigkeitssammlung

Beobachter und Abhängigkeitssammlungen sind wichtige Bestandteile des MVVM-Frameworks. Sie werden verwendet, um Änderungen in Daten zu beobachten und entsprechende Aktualisierungen durchzuführen. In unserem MVVM-Framework werden wir Watcherund- DepKlassen verwenden, um Beobachter- und Abhängigkeitserfassungsfunktionen zu implementieren.

// dep.js

export default class Dep {
  constructor() {
    this.subs = {};
  }

  addSub(target) {
    this.subs[target.uid] = target;
  }

  notify() {
    for (let uid in this.subs) {
      this.subs[uid].update();
    }
  }
}

// watcher.js

import Dep from "./dep";

var $uid = 0;
export default class Watcher {
  constructor(exp, scope, cb) {
    this.exp = exp;
    this.scope = scope;
    this.cb = cb;
    this.uid = $uid++;
    this.update();
  }

  get() {
    Dep.target = this;
    let newValue = Watcher.computeExpression(this.exp, this.scope);
    Dep.target = null;
    return newValue;
  }

  update() {
    let newValue = this.get();
    this.cb && this.cb(newValue);
  }

  static computeExpression(exp, scope) {
    let fn = new Function("scope", "with(scope){return " + exp + "}");
    return fn(scope);
  }
}

Im obigen Code haben wir DepKlassen zum Sammeln von Abhängigkeiten und Benachrichtigen von Aktualisierungen sowie WatcherKlassen zum Beobachten von Datenänderungen und zum Durchführen entsprechender Aktualisierungen definiert. DepDas Attribut der Klasse subswird zum Speichern aller WatcherInstanzen verwendet, es wird getin der Methode zum Sammeln von Abhängigkeiten verwendet und notifyes wird in der Methode zum Benachrichtigen der Aktualisierung verwendet. WatcherDie Attribute der Klasse uidwerden verwendet, um eindeutige Bezeichner zuzuweisen und so Watcherdie Einzigartigkeit jeder Instanz sicherzustellen. WatcherDas Attribut der Klasse expwird zum Speichern des zu beobachtenden Datenausdrucks, scopedas Attribut zum Speichern des Beobachtungsbereichs und cbdas Attribut zum Speichern der Rückruffunktion zum Aktualisieren von Daten verwendet.

5. Implementieren Sie Vue: Das MVVM-Framework ähnelt VUE

Schließlich werden wir Klassen verwenden Vue, um die oben implementierten Funktionen zu integrieren und ein einfaches MVVM-Framework ähnlich wie VUE zu vervollständigen.

// vue.js

import Observer from "./observer";
import Compiler from "./compiler";

class Vue {
  constructor(options) {
    this.$el = document.querySelector(options.el);
    this.$data = options.data || {};

    this._proxyData(this.$data);
    this._proxyMethods(options.methods);

    new Observer(this.$data);
    new Compiler(this);
  }

  _proxyData(data) {
    Object.keys(data).forEach((key) => {
      Object.defineProperty(this, key, {
        set(newValue) {
          data[key] = newValue;
        },
        get() {
          return data[key];
        },
      });
    });
  }

  _proxyMethods(methods) {
    if (methods && typeof methods === "object") {
      Object.keys(methods).forEach((key) => {
        this[key] = methods[key];
      });
    }
  }
}

window.Vue = Vue;

Im obigen Code haben wir VueKlassen definiert, um den Effekt des MVVM-Frameworks ähnlich wie VUE zu erzielen. VueDer Konstruktor der Klasse akzeptiert einen optionsParameter, der die Konfigurationsinformationen des MVVM-Frameworks enthält. Im Konstruktor konvertieren wir das Stammelement des MVVM-Frameworks $elin ein Dokumentfragment, rufen Compilerdie Klasse zum Kompilieren der Vorlage auf und verwenden Observerdie Klasse zum Entführen der Daten, wodurch die Grundfunktionen des MVVM-Frameworks realisiert werden.

6. Beispiele

Jetzt können wir mit unserem implementierten MVVM-Framework ein einfaches Beispiel erstellen. Zuerst müssen wir unser MVVM-Framework und Beispieldaten in HTML einführen und das Stammelement angeben.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../dist/vue.js"></script>
    <!-- <script src="../src/index.js"></script> -->
  </head>
  <body>
    <div id="app">
      <p>111-{
   
   {msg + ' Vue'}}-222</p>
      <p v-text="msg"></p>
      <input type="text" v-model="msg" />
      <button @click="handleClick">click</button>
    </div>
    <script type="text/javascript">
      var vm = new Vue({
        el: "#app",
        data: {
          msg: "Hello",
          info: {
            a: "111",
          },
        },
        methods: {
          handleClick: function () {
            console.log("handleClick", this.msg);
          },
        },
      });
    </script>
  </body>
</html>

Im obigen Beispiel haben wir { {}}die Syntax zum Anzeigen von Daten verwendet und v-modelAnweisungen @clickzum Realisieren der bidirektionalen Datenbindung und Ereignisüberwachung verwendet. Wenn der Benutzer Inhalte in das Eingabefeld eingibt, werden die Daten automatisch aktualisiert; wenn auf die Schaltfläche geklickt wird, ändern sich die Daten.

Das Obige ist der Prozess unseres einfachen Front-End-MVVM-Frameworks, das VUE ähnelt. Durch die Implementierung von Funktionen wie Datenhijacking, Vorlagenkompilierung, Beobachter und Abhängigkeitserfassung haben wir ein Front-End-Framework mit grundlegenden MVVM-Funktionen implementiert. Natürlich ist das eigentliche MVVM-Framework viel komplexer als dieses Beispiel, aber diese einfache Implementierung hat die Kernprinzipien und Implementierungsideen des MVVM-Frameworks demonstriert.

おすすめ

転載: blog.csdn.net/weixin_60895836/article/details/131966494