Schritte zur Vue-Batch-Update-DOM-Implementierung

Schritte zur Vue-Batch-Update-DOM-Implementierung

Szeneneinführung

In einer SFC (Single File Component) schreiben wir häufig eine Logik wie diese:

<Vorlage>
  <div>
    <span>{{ a }}</span>
    <span>{{ b }}</span>
  </div>  
</Vorlage>
<Skripttyp="Javascript">
Standard exportieren {
  Daten() {
    zurückkehren {
      eine: 0,
      b: 0
    }
  },
  erstellt() {
    // etwas Logikcode
    dies.a = 1
    dies.b = 2
  }
}
</Skript>

Wie Sie vielleicht wissen, fügt Vue nach Abschluss der Zuweisungsvorgänge von this.a und this.b die entsprechenden DOM-Aktualisierungsfunktionen von this.a und this.b in eine Mikrotask ein. Nachdem auf die Ausführung der Synchronisierungsaufgabe des Hauptthreads gewartet wurde, wird die Mikrotask aus der Warteschlange genommen und ausgeführt. Schauen wir uns an, wie es im offiziellen Vue-Dokument „In-depth Responsive Principles - Declaring Responsive Properties“ beschrieben wird:

Falls Sie es noch nicht bemerkt haben: Vue führt seine Aktualisierungen am DOM asynchron durch. Solange Datenänderungen erkannt werden, öffnet Vue eine Warteschlange und puffert alle Datenänderungen, die in derselben Ereignisschleife auftreten.

Also, wie erreicht Vue diese Fähigkeit? Um diese Frage zu beantworten, müssen wir tief in den Kern des Vue-Quellcodes eintauchen – das Responsive-Prinzip.

Hohe Reaktionsfähigkeit

Schauen wir uns zunächst an, was passiert, nachdem wir this.a und this.b Werte zugewiesen haben. Wenn Sie Vue CLI für die Entwicklung verwenden, wird es in der Datei main.js eine neue Vue()-Instanziierungsoperation geben. Da der Quellcode von Vue mithilfe von Flow geschrieben wird, erhöht sich der Aufwand für das Verständnis unsichtbar. Der Einfachheit halber schauen wir uns den Quellcode von vue.js direkt im Ordner „dist“ im npm vue-Paket an. Bei der Suche nach „function Vue“ habe ich den folgenden Quellcode gefunden:

Funktion Vue (Optionen) {
  wenn (!(diese Instanz von Vue)
  ) {
    warnen('Vue ist ein Konstruktor und sollte mit dem Schlüsselwort ‚new‘ aufgerufen werden');
  }
  this._init(Optionen);
}

Sehr einfacher Quellcode, der Quellcode ist wirklich nicht so schwierig, wie wir uns das vorgestellt haben! Nach einer solchen unerwarteten Überraschung suchen wir weiter nach der Funktion _init und schauen, was diese Funktion macht:

Vue.prototype._init = Funktion (Optionen) {
  var vm = dies;
  // eine UID
  vm._uid = uid$3++;

  var Starttag, Endtag;
  /* istanbul ignorieren wenn */
  wenn (config.performance && mark) {
    startTag = "vue-perf-start:" + (vm._uid);
    endTag = "vue-perf-end:" + (vm._uid);
    Markierung(Starttag);
  }

  // ein Flag, um zu verhindern, dass dies beobachtet wird
  vm._isVue = true;
  // Merge-Optionen
  if (Optionen && Optionen._istKomponente) {
    // Instanziierung interner Komponenten optimieren
    // da das Zusammenführen dynamischer Optionen ziemlich langsam ist und keine der
    // Interne Komponentenoptionen erfordern eine besondere Behandlung.
    initInternalComponent(vm, Optionen);
  } anders {
    vm.$optionen = mergeOptionen(
      Konstruktoroptionen auflösen(vm.constructor),
      Optionen || {},
      vm
    );
  }
  /* istanbul, sonst ignorieren */
  {
    initProxy(vm);
  }
  // wahres Ich enthüllen
  vm._self = vm;
  initLifecycle(vm);
  : InitEvents(vm);
  initRender(vm);
  callHook(vm, 'vorErstellen');
  initInjections(vm); // Injektionen vor Daten/Eigenschaften auflösen
  : Der initState(vm);
  initProvide(vm); // provide nach data/props auflösen
  callHook(vm, 'erstellt');

  /* istanbul ignorieren wenn */
  wenn (config.performance && mark) {
    vm._name = formatComponentName(vm, false);
    Markierung(EndeTag);
    messen(("vue " + (vm._name) + " init"), Starttag, Endtag);
  }

  wenn (vm.$options.el) {
    vm.$mount(vm.$options.el);
  }
}

Ignorieren wir die obigen Urteile vorerst und gehen wir direkt zur folgenden Hauptlogik über. Es ist ersichtlich, dass die _init-Funktion nacheinander initLifeCycle, initEvents, initRender, callHook, initInjections, initState, initProvide und die zweite callHook-Funktion ausführt. Aus der Benennung der Funktion können wir die spezifische Bedeutung erkennen. Im Allgemeinen ist dieser Code in die folgenden zwei Teile unterteilt

  1. Geben Sie nach Abschluss des Initialisierungslebenszyklus, der Ereignis-Hooks und der Rendering-Funktionen den Lebenszyklus „beforeCreate“ ein (führen Sie die Funktion „beforeCreate“ aus).
  2. Nachdem Sie die Initialisierung des Injektionswerts, des Status und des bereitgestellten Werts abgeschlossen haben, geben Sie den erstellten Lebenszyklus ein (führen Sie die erstellte Funktion aus).

Der für uns wichtige Teil des Prinzips der Datenreaktivität befindet sich in der Funktion initState. Sehen wir uns an, was diese Funktion macht:

Funktion initState (vm) {
  vm._watchers = [];
  var opts = vm.$optionen;
  wenn (opts.props) { initProps(vm, opts.props); }
  wenn (opts.methods) { initMethods(vm, opts.methods); }
  wenn (opts.data) {
    initData(vm);
  } anders {
    beobachten(vm._data = {}, true /* asRootData */);
  }
  wenn (opts.computed) { initComputed(vm, opts.computed); }
  wenn (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }
}

Hier sehen wir mehrere Konfigurationselemente, die beim Schreiben von SFC-Dateien häufig vorkommen: Props, Methods, Data, Computed und Watch. Wir konzentrieren unsere Aufmerksamkeit auf den Teil opts.data, der die Funktion initData ausführt:

Funktion initData (vm) {
  var Daten = vm.$Optionen.Daten;
  Daten = vm._Daten = Datentyp === 'Funktion'
    ? getData(Daten, vm)
    : Daten || {};
  wenn (!isPlainObject(data)) {
    Daten = {};
    warnen(
      'Datenfunktionen sollten ein Objekt zurückgeben:\n' +
      „https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function“,
      vm
    );
  }
  // Proxy-Daten auf der Instanz
  var keys = Objekt.keys(Daten);
  var props = vm.$options.props;
  var Methoden = vm.$options.methods;
  var i = Schlüssel.Länge;
  während (i--) {
    var Schlüssel = Schlüssel[i];
    {
      if (Methoden && hasOwn(Methoden, Schlüssel)) {
        warnen(
          ("Methode \"" + Schlüssel + "\" wurde bereits als Dateneigenschaft definiert."),
          vm
        );
      }
    }
    wenn (Requisiten und hasOwn (Requisiten, Schlüssel)) {
      warnen(
        "Die Dateneigenschaft \"" + Schlüssel + "\" ist bereits als Prop deklariert. " +
        "Stattdessen den Standardwert der Eigenschaft verwenden.",
        vm
      );
    } sonst wenn (!isReserved(Schlüssel)) {
      Proxy (vm, "_data", Schlüssel);
    }
  }
  // Daten beobachten
  beobachten(Daten, wahr /* asRootData */);
}

Wenn wir das Datenkonfigurationselement schreiben, definieren wir es als Funktion, sodass die Funktion getData hier ausgeführt wird:

Funktion getData (Daten, vm) {
  // #7573 Deaktivierung der Dep-Sammlung beim Aufrufen von Datengettern
  Ziel drücken();
  versuchen {
    gibt Daten zurück.Aufruf(vm, vm)
  } fangen (e) {
    Fehlerbehandlung(e, vm, "data()");
    zurückkehren {}
  Endlich
    popZiel();
  }
}

Die Funktion getData macht ganz einfach: Sie führt die Datenfunktion im Kontext der Komponenteninstanz aus. Beachten Sie, dass die Funktionen pushTarget und popTarget vor und nach der Ausführung der Datenfunktion ausgeführt werden. Auf diese beiden Funktionen werden wir später noch näher eingehen.

Nach der Ausführung der getData-Funktion kehren wir zur initData-Funktion zurück. Dahinter verbirgt sich eine Fehlerbeurteilung einer Schleife, die wir vorerst ignorieren. Kommen wir also zur Beobachtungsfunktion:

Funktion beobachten (Wert, asRootData) {
  if (!isObject(value) || Wertinstanz von VNode) {
    zurückkehren
  }
  var ob;
  wenn (hasOwn(Wert, '__ob__') && Wert.__ob__ Instanz von Observer) {
    ob = Wert.__ob__;
  } sonst wenn (
    sollteBeobachten &&
    !isServerRendering() &&
    (Array.isArray(Wert) || isPlainObject(Wert)) &&
    Objekt.isExtensible(Wert) &&
    !Wert._isVue
  ) {
    ob = neuer Beobachter(Wert);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}

Die Funktion „Observer“ erstellt einen Observer (ob) für das Datenobjekt, d. h., sie instanziiert den Observer. Was bewirkt die Instanziierung des Observers konkret? Schauen wir uns den Quellcode weiter an:

var Observer = Funktion Observer (Wert) {
  dieser.Wert = Wert;
  dies.dep = neues Dep();
  dies.vmCount = 0;
  def(Wert, '__ob__', dies);
  wenn (Array.isArray(Wert)) {
    wenn (hasProto) {
      protoAugment(Wert, ArrayMethoden);
    } anders {
      copyAugment(Wert, Array-Methoden, Array-Schlüssel);
    }
    dies.observeArray(Wert);
  } anders {
    dies.walk(Wert);
  }
}

Da die von uns definierte Datenfunktion normalerweise ein Objekt zurückgibt, werden wir uns hier nicht mit dem Array befassen. Führen Sie anschließend die Walk-Funktion weiter aus:

Observer.prototype.walk = Funktion walk (obj) {
  var keys = Objekt.keys(obj);
  für (var i = 0; i < Schlüssel.Länge; i++) {
    definiereReaktiv$$1(Objekt, Schlüssel[i]);
  }
}

Führen Sie für das von der Datenfunktion zurückgegebene Objekt, d. h. für jede aufzählbare Eigenschaft im Datenobjekt der Komponenteninstanz, die Funktion defineReactive$$1 aus:

Funktion defineReactive$$1 (
  Objekt,
  Schlüssel,
  Wert,
  benutzerdefinierterSetter,
  seicht
) {
  var dep = neues Dep();

  var Eigenschaft = Object.getOwnPropertyDescriptor(Obj, Schlüssel);
  if (Eigenschaft && Eigenschaft.konfigurierbar === false) {
    zurückkehren
  }

  // für vordefinierte Getter/Setter sorgen
  var getter = Eigenschaft && Eigenschaft.get;
  var Setter = Eigenschaft && Eigenschaft.set;
  wenn ((!getter || setter) && arguments.length === 2) {
    val = obj[Schlüssel];
  }

  var childOb = !shallow und observe(val);
  Objekt.defineProperty(Objekt, Schlüssel, {
    aufzählbar: wahr,
    konfigurierbar: true,
    get: Funktion reactiveGetter () {
      var-Wert = Getter? getter.call(Obj) : val;
      wenn (Dep.target) {
        dep.abhängig();
        wenn (KindOb) {
          childOb.dep.depend();
          wenn (Array.isArray(Wert)) {
            abhängigArray(Wert);
          }
        }
      }
      Rückgabewert
    },
    set: Funktion reactiveSetter (newVal) {
      var-Wert = Getter? getter.call(Obj) : val;
      /* eslint-deaktiviert keinen Selbstvergleich */
      wenn (neuerWert === Wert || (neuerWert !== neuerWert && Wert !== Wert)) {
        zurückkehren
      }
      /* eslint-enable kein Selbstvergleich */
      wenn (customSetter) {
        benutzerdefinierteSetter();
      }
      // #7981: für Accessor-Eigenschaften ohne Setter
      wenn (Getter und !Setter) { return }
      wenn (Setter) {
        setter.call(Objekt, neuerWert);
      } anders {
        val = neuerWert;
      }
      childOb = !shallow und beobachten (neuer Wert);
      dep.benachrichtigen();
    }
  });
}

In der Funktion defineReactive$$1 wird zunächst ein Abhängigkeitssammler instanziiert. Verwenden Sie dann Object.defineProperty, um den Getter (also die obige Get-Funktion) und den Setter (also die obige Set-Funktion) der Objekteigenschaft neu zu definieren.

Getter auslösen

In gewissem Sinne können Getter und Setter als Rückruffunktionen verstanden werden. Wenn der Wert einer Eigenschaft eines Objekts gelesen wird, wird die Get-Funktion (dh Getter) ausgelöst; wenn der Wert einer Eigenschaft eines Objekts festgelegt wird, wird die Set-Funktion (dh Setter) ausgelöst. Kehren wir zum ursprünglichen Beispiel zurück:

<Vorlage>
  <div>
    <span>{{ a }}</span>
    <span>{{ b }}</span>
  </div>  
</Vorlage>
<Skripttyp="Javascript">
Standard exportieren {
  Daten() {
    zurückkehren {
      eine: 0,
      b: 0
    }
  },
  erstellt() {
    // etwas Logikcode
    dies.a = 1
    dies.b = 2
  }
}
</Skript>

Hierbei werden die Werte der Eigenschaften a und b dieses Objektes gesetzt, sodass der Setter ausgelöst wird. Lassen Sie uns den oben festgelegten Funktionscode separat herausnehmen:

Funktion reactiveSetter (newVal) {
  var-Wert = Getter? getter.call(Objekt) : Wert;
  /* eslint-deaktiviert keinen Selbstvergleich */
  wenn (neuerWert === Wert || (neuerWert !== neuerWert && Wert !== Wert)) {
    zurückkehren
  }
  /* eslint-enable kein Selbstvergleich */
  wenn (customSetter) {
    benutzerdefinierteSetter();
  }
  // #7981: für Accessor-Eigenschaften ohne Setter
  wenn (Getter && !Setter) { return }
  wenn (Setter) {
    setter.call(Objekt, neuerWert);
  } anders {
    val = neuerWert;
  }
  childOb = !shallow und beobachten (neuer Wert);
  dep.benachrichtigen();
}

Der Setter führt zuerst den Getter aus:

Funktion reactiveGetter () {
  var-Wert = Getter? getter.call(Obj) : val;
  wenn (Dep.target) {
    dep.abhängig();
    wenn (KindOb) {
      childOb.dep.depend();
      wenn (Array.isArray(Wert)) {
        abhängigArray(Wert);
      }
    }
  }
  Rückgabewert
}

Der Getter prüft zunächst, ob Dep.target existiert. Als die Funktion getData zuvor ausgeführt wurde, war der Anfangswert von Dep.target null. Wann wurde ihm ein Wert zugewiesen? Als wir vorhin über die getData-Funktion sprachen, sahen wir eine pushTarget-Funktion und eine popTarget-Funktion. Der Quellcode dieser beiden Funktionen lautet wie folgt:

Dep.Ziel = null;
var ZielStack = [];

Funktion pushTarget (Ziel) {
  ZielStack.push(Ziel);
  Dep.Ziel = Ziel;
}

Funktion popTarget () {
  targetStack.pop();
  Dep.target = ZielStack[ZielStack.Länge - 1];
}

Um den Getter normal auszuführen, müssen Sie zuerst die PushTarget-Funktion ausführen. Lassen Sie uns herausfinden, wo die PushTarget-Funktion ausgeführt wird. Bei der Suche nach pushTarget in vue.js haben wir 5 Stellen gefunden. Abgesehen von der Definitionsstelle gibt es 4 Ausführungsstellen.
Der erste Ort, an dem die PushTarget-Funktion ausgeführt wird. Hier ist eine Funktion zur Fehlerbehandlung, die von normaler Logik nicht ausgelöst wird:

Funktion handleError (err, vm, info) {
  // Deaktivieren Sie die Deps-Verfolgung während der Verarbeitung des Fehlerhandlers, um ein mögliches unendliches Rendering zu vermeiden.
  // Siehe: https://github.com/vuejs/vuex/issues/1505
  Ziel drücken();
  versuchen {
    wenn (vm) {
      var aktuell = vm;
      während ((aktuell = aktuell.$übergeordnet)) {
        var hooks = cur.$options.errorCaptured;
        wenn (Haken) {
          für (var i = 0; i < hooks.length; i++) {
            versuchen {
              var capture = hooks[i].call(cur, err, vm, info) === false;
              wenn (erfassen) { return }
            } fangen (e) {
              globalHandleError(e, aktuell, 'errorCaptured-Hook');
            }
          }
        }
      }
    }
    globalHandleError(err, vm, info);
  Endlich
    popZiel();
  }
}

Der zweite Ort, an dem pushTarget ausgeführt wird. Hiermit wird die entsprechende Hook-Funktion aufgerufen. Es wird ausgelöst, wenn die entsprechende Hook-Funktion ausgeführt wird. Unsere aktuelle Operation liegt jedoch zwischen dem „beforeCreate“-Hook und dem „Created“-Hook und wurde nicht ausgelöst:

Funktion callHook (vm, hook) {
  // #7573 Deaktivierung der Dep-Sammlung beim Aufrufen von Lifecycle-Hooks
  Ziel drücken();
  var Handler = vm.$Optionen[Hook];
  var info = Haken + " Haken";
  wenn (Handler) {
    für (var i = 0, j = Handlerlänge; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info);
    }
  }
  wenn (vm._hasHookEvent) {
    vm.$emit('hook:' + hook);
  }
  popZiel();
}

Der dritte Ort, an dem pushTarget ausgeführt wird. Dies ist die Funktion, die ausgeführt wird, wenn der Watcher instanziiert wird. Beim Überprüfen des vorherigen Codes scheinen wir die Funktion des neuen Watchers nicht zu sehen:

Watcher.prototype.get = Funktion get () {
  drückeZiel(dieses);
  var-Wert;
  var vm = diese.vm;
  versuchen {
    Wert = this.getter.call(vm, vm);
  } fangen (e) {
    wenn (dieser.Benutzer) {
      handleError(e, vm, ("Getter für Watcher \"" + (this.expression) + "\""));
    } anders {
      werfen e
    }
  Endlich
    // "berühren" Sie jede Eigenschaft, damit sie alle verfolgt werden als
    // Abhängigkeiten für Deep Watching
    wenn (dies.tief) {
      durchqueren(Wert);
    }
    popZiel();
    dies.cleanupDeps();
  }
  Rückgabewert
}

Die vierte Stelle, an der pushTarget ausgeführt wird, ist die vorherige getData-Funktion. Die Funktion getData wird jedoch vor der Funktion defineReactive$$1 ausgeführt. Nach der Ausführung der getData-Funktion wurde Dep.target auf null zurückgesetzt.

Funktion getData (Daten, vm) {
  // #7573 Deaktivierung der Dep-Sammlung beim Aufrufen von Datengettern
  Ziel drücken();
  versuchen {
    gibt Daten zurück.Aufruf(vm, vm)
  } fangen (e) {
    Fehlerbehandlung(e, vm, "data()");
    zurückkehren {}
  Endlich
    popZiel();
  }
}

Es scheint, dass das direkte Auslösen des Setters die normale Ausführung der Logik im Getter nicht zulässt. Darüber hinaus haben wir auch festgestellt, dass die Logik des Setters nicht fortgesetzt werden kann, da Dep.target auch im Setter beurteilt wird. Wenn wir die Quelle von Dep.target nicht finden können, kann die Logik des Setters nicht fortgesetzt werden.

Abgangsziel suchen

Woher kommt also der Wert von Dep.target? Keine Sorge, gehen wir zurück zur Funktionsweise der _init-Funktion und schauen weiter nach unten:

Vue.prototype._init = Funktion (Optionen) {
  var vm = dies;
  // eine UID
  vm._uid = uid$3++;

  var Starttag, Endtag;
  /* istanbul ignorieren wenn */
  wenn (config.performance && mark) {
    startTag = "vue-perf-start:" + (vm._uid);
    endTag = "vue-perf-end:" + (vm._uid);
    Markierung(Starttag);
  }

  // ein Flag, um zu verhindern, dass dies beobachtet wird
  vm._isVue = true;
  // Merge-Optionen
  if (Optionen && Optionen._istKomponente) {
    // Instanziierung interner Komponenten optimieren
    // da das Zusammenführen dynamischer Optionen ziemlich langsam ist und keine der
    // Interne Komponentenoptionen erfordern eine besondere Behandlung.
    initInternalComponent(vm, Optionen);
  } anders {
    vm.$optionen = mergeOptionen(
      Konstruktoroptionen auflösen(vm.constructor),
      Optionen || {},
      vm
    );
  }
  /* istanbul, sonst ignorieren */
  {
    initProxy(vm);
  }
  // wahres Ich enthüllen
  vm._self = vm;
  initLifecycle(vm);
  : InitEvents(vm);
  initRender(vm);
  callHook(vm, 'vorErstellen');
  initInjections(vm); // Injektionen vor Daten/Eigenschaften auflösen
  : Der initState(vm);
  initProvide(vm); // provide nach data/props auflösen
  callHook(vm, 'erstellt');

  /* istanbul ignorieren wenn */
  wenn (config.performance && mark) {
    vm._name = formatComponentName(vm, false);
    Markierung(EndeTag);
    messen(("vue " + (vm._name) + " init"), Starttag, Endtag);
  }

  wenn (vm.$options.el) {
    vm.$mount(vm.$options.el);
  }
}

Wir haben festgestellt, dass am Ende der _init-Funktion die Funktion vm.$mount ausgeführt wurde. Was macht diese Funktion?

Vue.prototype.$mount = Funktion (
  el,
  feuchtigkeitsspendend
) {
  el = el && inBrowser? Abfrage(el): nicht definiert;
  returniere MountComponent(dieses, el, hydratisierend)
}

Lassen Sie uns nun die Funktion mountComponent aufrufen und einen Blick darauf werfen:

Funktion mountComponent (
  vm,
  el,
  feuchtigkeitsspendend
) {
  vm.$el = el;
  wenn (!vm.$options.render) {
    vm.$options.render = createEmptyVNode;
    {
      /* istanbul ignorieren wenn */
      wenn ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warnen(
          'Sie verwenden die reine Laufzeitversion von Vue, bei der die Vorlage ' +
          'Compiler ist nicht verfügbar. Entweder kompilieren Sie die Vorlagen vorab in ' +
          „Renderfunktionen oder verwenden Sie den im Compiler enthaltenen Build.“
          vm
        );
      } anders {
        warnen(
          „Komponente konnte nicht eingebunden werden: Vorlage oder Renderfunktion nicht definiert.“
          vm
        );
      }
    }
  }
  ruftHook(vm, 'vor Mount');

  var updateComponent;
  /* istanbul ignorieren wenn */
  wenn (config.performance && mark) {
    updateComponent = Funktion () {
      var name = vm._name;
      var id = vm._uid;
      var startTag = "vue-perf-start:" + id;
      var endTag = "vue-perf-end:" + id;

      Markierung(Starttag);
      var vnode = vm._render();
      Markierung(EndeTag);
      Maßnahme(("vue " + Name + " Rendern"), Starttag, Endtag);

      Markierung(Starttag);
      vm._update(vnode, hydratisieren);
      Markierung(EndeTag);
      Maßnahme(("vue " + Name + " Patch"), Starttag, Endtag);
    };
  } anders {
    updateComponent = Funktion () {
      vm._update(vm._render(), hydratisieren);
    };
  }

  // wir setzen dies auf vm._watcher im Konstruktor des Watchers
  // da der erste Patch des Watchers $forceUpdate aufrufen kann (z. B. innerhalb des untergeordneten
  // Mounted Hook der Komponente), der darauf basiert, dass vm._watcher bereits definiert ist
  neuer Watcher(vm, updateComponent, noop, {
    vorher: Funktion vorher () {
      wenn (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'vorUpdate');
      }
    }
  }, true /* istRenderWatcher */);
  hydratisierend = falsch;

  // manuell gemountete Instanz, auf „self“ gemountet aufrufen
  // Mounted wird für rendererstellte untergeordnete Komponenten in seinem eingefügten Hook aufgerufen.
  wenn (vm.$vnode == null) {
    vm._isMounted = wahr;
    callHook(vm, „gemountet“);
  }
  VM zurückgeben
}

Wir waren angenehm überrascht, dass es eine neue Watcher-Operation gibt! Es stimmt, dass Sie nach vielen Wendungen vielleicht denken, dass es keinen Ausweg gibt, aber plötzlich sehen Sie ein anderes Dorf mit Weiden und Blumen! Der hier instanziierte Watcher ist ein Watcher, der zum Aktualisieren des DOM verwendet wird. Es werden nacheinander alle Werte im Vorlagenabschnitt der SFC-Datei gelesen. Dies bedeutet, dass der entsprechende Getter ausgelöst wird.
Da der neue Watcher die Funktion watcher.get ausführt, die die Funktion pushTarget ausführt, wird Dep.target zugewiesen. Die Logik innerhalb des Getters wird reibungslos ausgeführt.

Getter

An diesem Punkt sind wir endlich beim Kern des Responsive-Prinzips von Vue angekommen. Kehren wir zum Getter zurück und sehen wir, was der Getter macht, nachdem Dep.target vorhanden ist:

Funktion reactiveGetter () {
  var-Wert = Getter? getter.call(Obj) : val;
  wenn (Dep.target) {
    dep.abhängig();
    wenn (KindOb) {
      childOb.dep.depend();
      wenn (Array.isArray(Wert)) {
        abhängigArray(Wert);
      }
    }
  }
  Rückgabewert
}

Ebenso werden wir uns nicht auf die Details der Verbesserung der Robustheit des Codes konzentrieren, sondern uns direkt auf die Hauptzeile konzentrieren. Wie Sie sehen, wird die Funktion dep.depend ausgeführt, wenn Dep.target vorhanden ist. Was macht diese Funktion? Schauen wir uns den Code an:

Dep.prototype.depend = Funktion abhängig() {
  wenn (Dep.target) {
    Dep.target.addDep(dies);
  }
}

Was Sie tun, ist auch sehr einfach. Die Funktion Dep.target.addDep wird ausgeführt. Da es sich bei Dep.target aber eigentlich um einen Watcher handelt, müssen wir zum Watcher-Code zurückkehren:

Watcher.prototype.addDep = Funktion addDep (dep) {
  var id = dep.id;
  wenn (!this.newDepIds.has(id)) {
    dies.newDepIds.add(id);
    dies.newDeps.push(dep);
    wenn (!this.depIds.has(id)) {
      dep.addSub(dies);
    }
  }
}

Ebenso ignorieren wir einige kleinere Logikverarbeitungen und konzentrieren uns auf die Funktion dep.addSub:

Dep.prototype.addSub = Funktion addSub (sub) {
  dies.subs.push(sub);
}

Es handelt sich außerdem um eine sehr einfache Logik, die den Beobachter als Abonnenten zum Zwischenspeichern in das Array schiebt. An diesem Punkt ist die gesamte Logik des Getters abgeschlossen. Danach wird die Funktion popTarget ausgeführt und Dep.target auf null zurückgesetzt.

Setter

Kommen wir noch einmal zurück zum Geschäftscode:

<Vorlage>
  <div>
    <span>{{ a }}</span>
    <span>{{ b }}</span>
  </div>  
</Vorlage>
<Skripttyp="Javascript">
Standard exportieren {
 Daten() {
    zurückkehren {
      eine: 0,
      b: 0
    }
  },
  erstellt() {
    // etwas Logikcode
    dies.a = 1
    dies.b = 2
  }
}
</Skript>

Im erstellten Lebenszyklus haben wir den Setter zweimal ausgelöst. Die Logik der Setter-Ausführung lautet wie folgt:

Funktion reactiveSetter (newVal) {
  var-Wert = Getter? getter.call(Obj) : val;
  /* eslint-deaktiviert keinen Selbstvergleich */
  wenn (neuerWert === Wert || (neuerWert !== neuerWert && Wert !== Wert)) {
    zurückkehren
  }
  /* eslint-enable kein Selbstvergleich */
  wenn (customSetter) {
    benutzerdefinierterSetter();
  }
  // #7981: für Accessor-Eigenschaften ohne Setter
  wenn (Getter und !Setter) { return }
  wenn (Setter) {
    setter.call(Objekt, neuerWert);
  } anders {
    val = neuerWert;
  }
  childOb = !shallow und beobachten (neuer Wert);
  dep.benachrichtigen();
}

Hier müssen wir uns nur auf die letzte vom Setter ausgeführte Funktion konzentrieren: dep.notify(). Schauen wir uns an, was diese Funktion macht:

Dep.prototype.notify = Funktion benachrichtigen () {
  // zuerst die Abonnentenliste stabilisieren
  var subs = this.subs.slice();
  wenn (!config.async) {
    // Subs werden im Scheduler nicht sortiert, wenn sie nicht asynchron laufen
    // wir müssen sie jetzt sortieren, um sicherzustellen, dass sie richtig ausgelöst werden
    // Befehl
    subs.sort(Funktion (a, b) { return a.id - b.id; });
  }
  für (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
}

Jedes Element von This.subs ist ein Beobachter. Im Getter-Abschnitt oben haben wir nur einen Beobachter gesammelt. Da der Setter zweimal ausgelöst wird, subs[0].update(), bedeutet dies, dass die Funktion watcher.update() zweimal ausgeführt wird. Schauen wir uns an, was diese Funktion macht:

Watcher.prototype.update = Funktion update () {
  /* istanbul, sonst ignorieren */
  wenn (dies.lazy) {
    dies.schmutzig = wahr;
  } sonst wenn (diese.sync) {
    dies.laufen();
  } anders {
    Warteschlangenwächter(dies);
  }
}

Wie üblich springen wir direkt in die queueWatcher-Funktion:

Funktion queueWatcher (Beobachter) {
  var id = watcher.id;
  wenn (hat[id] == null) {
    hat[id] = wahr;
    wenn (!spülen) {
      Warteschlange.push(Beobachter);
    } anders {
      // wenn bereits geleert wird, spleißen Sie den Beobachter basierend auf seiner ID
      // wenn seine ID bereits überschritten ist, wird es sofort als nächstes ausgeführt.
      var i = Warteschlangenlänge – 1;
      während (i > Index && Warteschlange[i].id > Watcher.id) {
        ich--;
      }
      Warteschlange.splice(i + 1, 0, Beobachter);
    }
    // den Flush in die Warteschlange stellen
    wenn (!warten) {
      warten = wahr;

      wenn (!config.async) {
        : flushSchedulerQueue();
        zurückkehren
      }
      nächsterTick(flushSchedulerQueue);
    }
  }
}

Da die ID dieselbe ist, wird die Rückruffunktion des Beobachters nur einmal in die Warteschlange gestellt. Hier sehen wir wieder ein bekanntes Gesicht: nextTick.

Funktion nextTick (cb, ctx) {
  var _auflösen;
  Rückrufe.push(Funktion () {
    wenn (cb) {
      versuchen {
        cb.call(ctx);
      } fangen (e) {
        Fehler behandeln(e, ctx, 'nächsterTick');
      }
    } sonst wenn (_resolve) {
      _auflösen(ctx);
    }
  });
  wenn (!ausstehend) {
    ausstehend = wahr;
    timerFunc();
  }
  // $flow-deaktivierungszeile
  wenn (!cb && Typ von Promise !== 'undefined') {
    returniere neues Promise(Funktion (Auflösung) {
      _resolve = lösen;
    })
  }
}

Die Funktion nextTick umschließt die Callback-Funktion erneut und führt timerFunc() aus

var Timerfunktion;

// Das nextTick-Verhalten nutzt die Microtask-Warteschlange, auf die zugegriffen werden kann
// entweder über natives Promise.then oder MutationObserver.
// MutationObserver hat eine breitere Unterstützung, ist jedoch ernsthaft fehlerhaft in
// UIWebView in iOS >= 9.3.3, wenn es in Touch-Event-Handlern ausgelöst wird. Es
// funktioniert nach mehrmaligem Auslösen nicht mehr... also, wenn native
// Das Versprechen ist verfügbar, wir werden es verwenden:
/* Istanbul, nächstes ignorieren, $flow-disable-line */
wenn (Typ von Promise !== 'undefined' und isNative(Promise)) {
  var p = Promise.resolve();
  Timerfunktion = Funktion () {
    p.then(flushCallbacks);
    // Bei problematischen UIWebViews bricht Promise.then nicht komplett ab, aber
    // es kann in einem seltsamen Zustand stecken bleiben, in dem Callbacks in den
    // Microtask-Warteschlange, aber die Warteschlange wird nicht geleert, bis der Browser
    // muss noch andere Arbeit erledigen, z.B. einen Timer bedienen. Daher können wir
    // Durch Hinzufügen eines leeren Timers das Leeren der Microtask-Warteschlange „erzwingen“.
    wenn (istIOS) { setTimeout(noop); }
  };
  istUsingMicroTask = true;
} sonst wenn (!isIE && typeof MutationObserver !== 'undefined' && (
  istNative(MutationObserver) ||
  // PhantomJS und iOS 7.x
  MutationObserver.toString() === '[Objekt MutationObserverConstructor]'
)) {
  // MutationObserver verwenden, wenn natives Promise nicht verfügbar ist,
  // zB PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver ist in IE11 unzuverlässig)
  Var Zähler = 1;
  var Beobachter = neuer MutationObserver(flushCallbacks);
  var textNode = document.createTextNode(String(counter));
  Beobachter.beobachten(textNode, {
    Zeichendaten: true
  });
  Timerfunktion = Funktion () {
    Zähler = (Zähler + 1) % 2;
    textNode.data = String(Zähler);
  };
  istUsingMicroTask = true;
} sonst wenn (Typ von setImmediate !== 'undefined' und isNative(setImmediate)) {
  // Fallback auf setImmediate.
  // Technisch nutzt es die (Makro-)Task-Warteschlange,
  // aber es ist immer noch eine bessere Wahl als setTimeout.
  Timerfunktion = Funktion () {
    setImmediate(flushCallbacks);
  };
} anders {
  // Fallback auf setTimeout.
  Timerfunktion = Funktion () {
    setTimeout(flushCallbacks, 0);
  };
}

Die TimerFunc-Funktion ist eine sanfte Herabstufung der Mikrotask. Er ruft Promise, MutationObserver, setImmediate und setTimeout nacheinander entsprechend der Unterstützungsstufe der Umgebung auf. Und führen Sie die Rückruffunktion in der entsprechenden Mikrotask- oder simulierten Mikrotask-Warteschlange aus.

Funktion flushSchedulerQueue () {
  currentFlushTimestamp = getNow();
  Spülen = wahr;
  var-Watcher, ID;

  //Warteschlange vor dem Leeren sortieren.
  // Dadurch wird Folgendes sichergestellt:
  // 1. Komponenten werden vom übergeordneten zum untergeordneten Element aktualisiert. (weil das übergeordnete Element immer
  // vor dem Kind erstellt)
  // 2. Die Benutzer-Watcher einer Komponente werden vor ihrem Render-Watcher ausgeführt (weil
  // Benutzer-Watcher werden vor dem Render-Watcher erstellt)
  // 3. Wenn eine Komponente während des Watcher-Laufs einer übergeordneten Komponente zerstört wird,
  // seine Beobachter können übersprungen werden.
  Warteschlange.sortieren(Funktion (a, b) { return a.id - b.id; });

  // Länge nicht zwischenspeichern, da sonst weitere Beobachter gepusht werden könnten
  // während wir vorhandene Beobachter ausführen
  für (Index = 0; Index < Warteschlangenlänge; Index++) {
    Beobachter = Warteschlange[Index];
    wenn (watcher.before) {
      watcher.vorher();
    }
    id = Beobachter.id;
    hat[id] = null;
    watcher.run();
    // im Dev-Build zyklische Updates prüfen und stoppen.
    wenn (hat[id] != null) {
      kreisförmig[id] = (kreisförmig[id] || 0) + 1;
      wenn (rundschreiben[id] > MAX_UPDATE_COUNT) {
        warnen(
          'Möglicherweise liegt eine Endlosschleife für die Aktualisierung vor ' + (
            watcher.user
              ? ("im Watcher mit Ausdruck \"" + (watcher.expression) + "\"")
              : "in einer Komponenten-Renderfunktion."
          ),
          watcher.vm
        );
        brechen
      }
    }
  }

  // Bewahren Sie Kopien der Post-Warteschlangen auf, bevor Sie den Status zurücksetzen
  var aktivierteQueue = aktivierteChildren.slice();
  var aktualisierteQueue = queue.slice();

  : ResetSchedulerState();

  // Aufruf der aktualisierten Komponente und der aktivierten Hooks
  rufActivatedHooks auf (activatedQueue);
  rufUpdatedHooks auf(aktualisierteWarteschlange);

  // Devtool-Hook
  /* istanbul ignorieren wenn */
  wenn (devtools && config.devtools) {
    devtools.emit('spülen');
  }
}

Die Kernlogik der Rückruffunktion besteht darin, die Funktion watcher.run auszuführen:

Watcher.prototype.run = Funktion ausführen () {
  wenn (dies.aktiv) {
    var-Wert = this.get();
    Wenn (
      Wert !== dieser.Wert ||
      // Deep Watcher und Watcher für Objekte/Arrays sollten auch dann ausgelöst werden,
      // wenn der Wert gleich ist, da der Wert
      // sind mutiert.
      istObjekt(Wert) ||
      das.tief
    ) {
      // neuen Wert setzen
      var alterWert = dieser.Wert;
      dieser.Wert = Wert;
      wenn (dieser.Benutzer) {
        versuchen {
          this.cb.call(this.vm, Wert, alterWert);
        } fangen (e) {
          handleError(e, this.vm, ("Rückruf für Watcher \"" + (this.expression) + "\""));
        }
      } anders {
        this.cb.call(this.vm, Wert, alterWert);
      }
    }
  }
}

Führen Sie die Funktion this.cb aus, die die Rückruffunktion des Watchers ist. An diesem Punkt ist die gesamte Logik abgeschlossen.

Zusammenfassen

Kommen wir noch einmal zum Geschäftsszenario zurück:

<Vorlage>
  <div>
    <span>{{ a }}</span>
    <span>{{ b }}</span>
  </div>  
</Vorlage>
<Skripttyp="Javascript">
Standard exportieren {
  Daten() {
    zurückkehren {
      eine: 0,
      b: 0
    }
  },
  erstellt() {
    // etwas Logikcode
    dies.a = 1
    dies.b = 2
  }
}
</Skript>

Obwohl wir den Setter zweimal ausgelöst haben, wurde die entsprechende Rendering-Funktion im Mikrotask nur einmal ausgeführt. Das heißt, nachdem die Funktion dep.notify eine Benachrichtigung gesendet hat, dedupliziert Vue den entsprechenden Watcher, stellt ihn in die Warteschlange und führt schließlich den Rückruf aus.

Es ist ersichtlich, dass die beiden Zuweisungsvorgänge tatsächlich dieselbe Rendering-Funktion auslösen, die mehrere DOMs aktualisiert. Dies wird als Batch-Aktualisierung des DOM bezeichnet.

Dies ist das Ende dieses Artikels über die Implementierungsschritte der Vue-Batchaktualisierung des DOM. Weitere relevante Inhalte zur Vue-Batchaktualisierung des DOM finden Sie in den vorherigen Artikeln von 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:
  • VUE aktualisiert DOM asynchron – Verwendung von $nextTick zum Lösen von DOM-Ansichtsproblemen
  • Lösen Sie das Problem, dass der virtuelle DOM in Vue nicht in Echtzeit aktualisiert werden kann
  • Detaillierte Erklärung der asynchronen DOM-Update-Strategie und des nextTick aus dem Vue.js-Quellcode

<<:  Grafisches Tutorial zur Installation und Konfiguration von mysql 8.0.16 winx64.zip

>>:  So lösen Sie das Problem „Nginx 503-Dienst vorübergehend nicht verfügbar“

Artikel empfehlen

Centos7-Installation des in Nginx integrierten Lua-Beispielcodes

Vorwort Der von mir verwendete Computer ist ein M...

HTML+CSS-Implementierungscode für abgerundete Rechtecke

Mir war langweilig und plötzlich fiel mir die Impl...

Vue verwendet dynamische Komponenten, um einen TAB-Umschalteffekt zu erzielen

Inhaltsverzeichnis Problembeschreibung Was ist di...

Eine kurze Analyse des Unterschieds zwischen statisch und selbst in PHP-Klassen

Verwenden Sie self:: oder __CLASS__, um eine stat...

Centos7.3 So installieren und implementieren Sie Nginx und konfigurieren https

Installationsumgebung 1. gcc-Installation Um ngin...

Eine kurze Erläuterung des Sperrbereichs der MySQL-Next-Key-Sperre

Vorwort Eines Tages wurde ich plötzlich nach der ...

Ein Artikel, der Ihnen hilft, jQuery-Animationen zu verstehen

Inhaltsverzeichnis 1. Steuern Sie die Anzeige und...

Eine vollständige Aufzeichnung eines Mysql-Deadlock-Fehlerbehebungsprozesses

Vorwort Die Datenbank-Deadlocks, die ich zuvor er...

Tools zur Bildoptimierung für Webseiten und Tipps zur Verwendung

Als grundlegendes Element einer Webseite sind Bil...

Tatsächlicher Datensatz zur Wiederherstellung der MySQL-Datenbank nach Zeitpunkt

Einführung: MySQL-Datenbankwiederherstellung nach...

Tutorial-Diagramm zur Installation von TomCat unter Windows 10

Installieren Sie TomCat unter Windows Dieser Arti...

Praxis der Vue Global Custom-Anweisung Modal Drag

Inhaltsverzeichnis Hintergrund Umsetzungsideen Er...

So implementieren Sie E-Mail-Benachrichtigungen in Zabbix

Umgesetzt gemäß Online-Tutorial. zabbix3.4, mithi...