Vue-Interpretation der responsiven Prinzip-Quellcode-Analyse

Vue-Interpretation der responsiven Prinzip-Quellcode-Analyse

Schauen Sie sich zunächst das Bild an, um den allgemeinen Vorgang und die Vorgehensweise zu verstehen

Initialisierung

Wenn ein neuer Vue initialisiert wird, werden auch die Eigenschaften und Daten unserer Komponente initialisiert. Da dieser Artikel hauptsächlich die Reaktionsfähigkeit einführt, werde ich die anderen Aspekte nicht im Detail erläutern. Werfen wir einen Blick auf den Quellcode.

Quellcodeadresse: src/core/instance/init.js – Zeile 15

Exportfunktion initMixin (Vue: Klasse<Komponente>) {
  // Füge die Methode _init zum Prototyp hinzu Vue.prototype._init = function (options?: Object) {
    ...
    vm._self = vm
    initLifecycle(vm) // Initialisiere Instanzeigenschaften und -daten: $parent, $children, $refs, $root, _watcher... usw. initEvents(vm) // Initialisiere Ereignisse: $on, $off, $emit, $once
    initRender(vm) // Rendering initialisieren: render, mixin
    callHook(vm, 'beforeCreate') // Rufe die Lifecycle-Hook-Funktion auf initInjections(vm) // Initialisiere Injection
    initState(vm) // Komponentendaten initialisieren: Eigenschaften, Daten, Methoden, Überwachung, berechnet
    initProvide(vm) // Provide initialisieren
    callHook(vm, „erstellt“) // Lebenszyklus-Hook-Funktion aufrufen …
  }
}

Hier werden zur Initialisierung viele Methoden aufgerufen, die jeweils unterschiedliche Dinge tun, und die Reaktionsfähigkeit bezieht sich hauptsächlich auf die Dateneigenschaften und Daten innerhalb der Komponente. Dieser Teil befindet sich in der Methode initState(). Sehen wir uns also den Quellcode dieser Methode an.

initState()

Quellcodeadresse: src/core/instance/state.js – Zeile 49

Exportfunktion initState (vm: Komponente) {
  vm._watchers = []
  const opts = vm.$optionen
  // Requisiten initialisieren
  wenn (opts.props) initProps(vm, opts.props)
  // Initialisierungsmethoden
  wenn (opts.methods) initMethods(vm, opts.methods)
  //Daten initialisieren 
  wenn (opts.data) {
    initData(vm)
  } anders {
    // Wenn keine Daten vorhanden sind, ist der Standardwert ein leeres Objekt, und beobachten Sie (vm._data = {}, true /* asRootData */).
  }
  // Berechnete Werte initialisieren
  wenn (opts.computed) initComputed(vm, opts.computed)
  // Uhr initialisieren
  wenn (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

Auch hier werden eine Reihe von Initialisierungsmethoden aufgerufen. Kommen wir gleich zum Punkt und nehmen diejenigen, die sich auf unsere responsiven Daten beziehen, nämlich initProps(), initData() und observe()
Wählen Sie sie weiterhin einzeln aus und verstehen Sie den gesamten Reaktionsprozess.

initProps()

Quellcodeadresse: src/core/instance/state.js – Zeile 65

Die wichtigsten Dinge, die hier getan werden, sind:

  • Durchlaufen Sie die von der übergeordneten Komponente übergebene Requisitenliste.
  • Überprüfen Sie den Namen, den Typ, das Standardattribut usw. jedes Attributs. Wenn kein Problem vorliegt, rufen Sie defineReactive auf, um es auf responsive einzustellen
  • Verwenden Sie dann proxy(), um die Eigenschaften an die aktuelle Instanz weiterzuleiten, z. B. indem Sie vm._props.xx in vm.xx ändern, und Sie können darauf zugreifen
Funktion initProps (vm: Komponente, propsOptions: Objekt) {
  // Die übergeordnete Komponente übergibt Eigenschaften an die untergeordnete Komponente
  const propsData = vm.$options.propsData || {}
  // Endgültige Eigenschaften nach der Konvertierung
  const props = vm._props = {}
  //Speichere den Schlüssel der Props. Auch wenn der Props-Wert leer ist, wird der Schlüssel darin enthalten sein const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // Props von Nicht-Root-Instanzen konvertieren
  wenn (!isRoot) {
    ToggleObserving(false)
  }
  für (const key in propsOptions) {
    Tasten.drücken(Taste)
    // Überprüfen Sie den Props-Typ, die Standardattribute usw. const value = validateProp(key, propsOptions, propsData, vm)
    // In einer Nicht-Produktionsumgebung, wenn (process.env.NODE_ENV !== 'production') {
      const hyphenatedKey = Bindestrich(Schlüssel)
      wenn (istReserviertesAttribut(Schlüssel mit Bindestrich) ||
          config.isReservedAttr(Schlüssel mit Bindestrich)) {
        warnen(`hyphenatedKey ist eine reservierte Eigenschaft und kann nicht als Komponenteneigenschaft verwendet werden`)
      }
      // Props so einstellen, dass sie reagieren defineReactive(props, key, value, () => {
        // Warnen, wenn der Benutzer Eigenschaften ändert, if (!isRoot && !isUpdatingChildComponent) {
          warnen(`Direktes Ändern von Requisiten vermeiden`)
        }
      })
    } anders {
      //Requisiten so einstellen, dass sie reagieren. defineReactive(props, key, value)
    }
    // Übertrage Eigenschaften, die sich nicht auf der Standard-VM befinden, auf die Instanz //, sodass auf vm._props.xx über vm.xx zugegriffen werden kann, if (!(key in vm)) {
      Proxy (vm, „_props“, Schlüssel)
    }
  }
  umschaltenBeobachtung(true)
}

initData()

Quellcodeadresse: src/core/instance/state.js – Zeile 113

Die wichtigsten Dinge, die hier getan werden, sind:

  • Initialisieren Sie Daten und legen Sie die Schlüssel fest
  • Durchlaufen Sie die Schlüsselsammlung, um festzustellen, ob es einen doppelten Namen mit dem Eigenschaftsnamen in Requisiten oder dem Methodennamen in Methoden gibt.
  • Wenn kein Problem vorliegt, leiten Sie jedes Attribut in den Daten über Proxy () an die aktuelle Instanz weiter und Sie können über this.xx darauf zugreifen.
  • Rufen Sie abschließend „object“ auf, um die gesamten Daten zu überwachen
Funktion initData (vm: Komponente) {
  // Daten der aktuellen Instanz abrufen 
  let data = vm.$options.data
  // Bestimmen Sie den Datentyp data = vm._data = typeof data === 'function'
    ? getData(Daten, vm)
    : Daten || {}
  wenn (!isPlainObject(data)) {
    Daten = {}
    process.env.NODE_ENV !== 'Produktion' && warn(`Datenfunktion sollte ein Objekt zurückgeben`)
  }
  // Den Datenattributnamensatz der aktuellen Instanz abrufen const keys = Object.keys(data)
  // Die Props der aktuellen Instanz abrufen 
  const props = vm.$options.props
  // Holen Sie das Methodenobjekt der aktuellen Instanz const methods = vm.$options.methods
  sei i = Schlüssel.Länge
  während (i--) {
    const Schlüssel = Schlüssel[i]
    // Bestimmen Sie in einer Nicht-Produktionsumgebung, ob die Methode in Methoden in Eigenschaften vorhanden ist, wenn (process.env.NODE_ENV !== 'production') {
      if (Methoden && hasOwn(Methoden, Schlüssel)) {
        warnen(`Methode kann nicht wiederholt deklariert werden`)
      }
    }
    // In einer Nicht-Produktionsumgebung bestimmen, ob das Attribut in den Daten in Props vorhanden ist, wenn (Props && hasOwn(Props, Schlüssel)) {
      process.env.NODE_ENV !== 'Produktion' && warn(`Eigenschaften können nicht wiederholt deklariert werden`)
    } sonst wenn (!isReserved(Schlüssel)) {
      // Wenn die Namen nicht gleich sind, Proxy zu vm // vm._data.xx kann über vm.xx auf Proxy(vm, `_data`, Schlüssel) zugreifen
    }
  }
  // Auf Daten warten
  beobachte(Daten, wahr /* alsRootData */)
}

beobachten()

Quellcodeadresse: src/core/observer/index.js – Zeile 110

Diese Methode wird hauptsächlich verwendet, um den Daten Listener hinzuzufügen.

Die wichtigsten Dinge, die hier getan werden, sind:

  • Wenn es sich um einen Vnode-Objekttyp oder nicht um einen Referenztyp handelt, beenden Sie einfach
  • Andernfalls fügen Sie den Daten, denen kein Observer hinzugefügt wurde, einen Observer, also einen Listener, hinzu.
Exportfunktion beobachten (Wert: beliebig, asRootData: ?boolean): Beobachter | void {
  // Wenn es nicht vom Typ „Objekt“ oder VNode ist, direkt zurückgeben, wenn (!isObject(Wert) || Wertinstanz von VNode) {
    zurückkehren
  }
  let ob: Beobachter | void
  // Zwischengespeichertes Objekt verwenden if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = Wert.__ob__
  } sonst wenn (
    sollteBeobachten &&
    !isServerRendering() &&
    (Array.isArray(Wert) || isPlainObject(Wert)) &&
    Objekt.isExtensible(Wert) &&
    !Wert._isVue
  ) {
    //Erstelle einen Listener ob = neuer Observer(Wert)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

Beobachter

Quellcode-Adresse: src/core/observer/index.js – Zeile 37

Dies ist eine Klasse, die normale Daten in beobachtbare Daten umwandelt.

Die wichtigsten Dinge, die hier getan werden, sind:

  • Markieren Sie den aktuellen Wert als responsives Attribut, um wiederholte Vorgänge zu vermeiden
  • Bestimmen Sie dann den Datentyp
    • Wenn es ein Objekt ist, iterieren Sie über das Objekt und rufen Sie defineReactive() auf, um ein reaktionsfähiges Objekt zu erstellen.
    • Wenn es ein Array ist, iterieren Sie über das Array und rufen Sie observe() auf, um jedes Element zu überwachen.
Exportklasse Beobachter {
  Wert: beliebig;
  abh.: Abh.;
  vmCount: Zahl; // Die Anzahl der VMs im Root-Objektkonstruktor (Wert: beliebig) {
    dieser.Wert = Wert
    dies.dep = neues Dep()
    this.vmCount = 0
    // Füge dem Wert das Attribut __ob__ hinzu, dessen Wert die Observe-Instanz des Werts ist // Dies zeigt an, dass es reagiert. Der Zweck besteht darin, beim Durchlaufen des Objekts direkt zu überspringen, um wiederholte Vorgänge zu vermeiden def(value, '__ob__', this)
    //Typbeurteilung if (Array.isArray(value)) {
      // Bestimmen Sie, ob das Array __proty__ hat
      wenn (hasProto) {
        // Wenn ja, schreiben Sie die Array-Methode protoAugment(value, arrayMethods) neu.
      } anders {
        // Wenn nicht, definieren Sie den Eigenschaftswert über def, d. h. Object.defineProperty copyAugment (value, arrayMethods, arrayKeys).
      }
      this.observeArray(Wert)
    } anders {
      this.walk(Wert)
    }
  }
  // Wenn es ein Objekttyp ist, walk (obj: Object) {
    const keys = Objekt.keys(obj)
    // Alle Eigenschaften des Objekts durchlaufen und in ein responsives Objekt umwandeln. Getter und Setter dynamisch hinzufügen, um eine bidirektionale Bindung zu erreichen for (let i = 0; i < keys.length; i++) {
      definiereReaktiv(Objekt, Schlüssel[i])
    }
  }
  //ObserveArray (Elemente: Array<beliebig>) {
    // Durchlaufe das Array und überwache jedes Element auf (let i = 0, l = items.length; i < l; i++) {
      beobachten(Elemente[i])
    }
  }
}

defineReactive()

Quellcodeadresse: src/core/observer/index.js – Zeile 135

Der Zweck dieser Methode besteht darin, ein responsives Objekt zu definieren

Die wichtigsten Dinge, die hier getan werden, sind:

  • Initialisieren Sie zuerst eine Dep-Instanz
  • Wenn es sich um ein Objekt handelt, rufen Sie „object“ auf und listen Sie rekursiv auf, um sicherzustellen, dass es sich unabhängig von der Tiefe der Verschachtelung der Struktur um ein reagierendes Objekt handeln kann.
  • Rufen Sie dann Object.defineProperty() auf, um den Getter und Getter der Objekteigenschaft zu kapern
  • Wenn der Getter beim Abrufen ausgelöst wird, ruft er dep.depend() auf, um den Beobachter in das abhängige Array subs zu verschieben, d. h. in die abhängige Sammlung
  • Wenn aktualisiert, wird der Trigger-Setter Folgendes tun
    • Der neue Wert hat sich nicht geändert oder es gibt keine Setter-Eigenschaft.
    • Wenn der neue Wert ein Objekt ist, rufen Sie observe() auf, um rekursiv zu überwachen
    • Rufen Sie dann dep.notify() auf, um Updates zu versenden
Exportfunktion defineReactive (
  obj: Objekt,
  Schlüssel: Zeichenfolge,
  Wert: beliebig,
  customSetter?: ?Funktion,
  flach?: Boolesch
) {

  //Erstellen einer Dep-Instanz const dep = new Dep()
  // Den Eigenschaftsdeskriptor des Objekts abrufen const property = Object.getOwnPropertyDescriptor(obj, key)
  if (Eigenschaft && Eigenschaft.konfigurierbar === false) {
    zurückkehren
  }
  // Benutzerdefinierte Getter und Setter abrufen
  const getter = Eigenschaft && Eigenschaft.get
  const setter = Eigenschaft && Eigenschaft.set
  wenn ((!getter || setter) && arguments.length === 2) {
    val = obj[Schlüssel]
  }
  // Wenn val ein Objekt ist, rekursiv beobachten // Durch rekursives Aufrufen von observe kann sichergestellt werden, dass es sich unabhängig davon, wie tief die Objektstruktur verschachtelt ist, um ein responsives Objekt handeln kann. let childOb = !shallow && observe(val)
  //Fangen Sie die Getter und Setter der Objekteigenschaften ab
  Objekt.defineProperty(Objekt, Schlüssel, {
    aufzählbar: wahr,
    konfigurierbar: true,
    // Getter abfangen, und die Funktion wird ausgelöst, wenn der Wert abgerufen wird get: function reactiveGetter () {
      konstanter Wert = Getter? getter.call(obj) : Wert
      // Führe eine Abhängigkeitssammlung durch // Greife beim Initialisieren des Rendering-Watchers auf das Objekt zu, das eine bidirektionale Bindung erfordert, und löse dadurch die Get-Funktion aus, if (Dep.target) {
        dep.abhängig()
        wenn (KindOb) {
          childOb.dep.depend()
          wenn (Array.isArray(Wert)) {
            abhängigArray(Wert)
          }
        }
      }
      Rückgabewert
    },
    //Setzer abfangen, wenn sich der Wert ändert, wird die Funktion ausgelöst set: function reactiveSetter (newVal) {
      konstanter Wert = Getter? getter.call(obj) : Wert
      // Bestimmen, ob eine Änderung aufgetreten ist, wenn (newVal === value || (newVal !== newVal && value !== value)) {
        zurückkehren
      }
      wenn (Prozess.Umgebung.NODE_ENV !== 'Produktion' und customSetter) {
        benutzerdefinierteSetter()
      }
      // Accessor-Eigenschaft ohne Setter if (Getter && !Setter) return
      wenn (Setter) {
        setter.call(Objekt, neuerWert)
      } anders {
        val = neuerWert
      }
      // Wenn der neue Wert ein Objekt ist, beobachten Sie rekursiv childOb = !shallow && observe(newVal)
      // Update versenden dep.notify()
    }
  })
}

Wie oben erwähnt, erfolgt die Abhängigkeitssammlung über dep.depend. Man kann sagen, dass Dep der Kern der gesamten Getter-Abhängigkeitssammlung ist.

Abhängigkeitssammlung

Der Kern der Abhängigkeitssammlung ist Dep und untrennbar mit Watcher verbunden. Werfen wir einen Blick darauf

Dep

Quellcode-Adresse: src/core/observer/dep.js

Dies ist eine Klasse, die tatsächlich Watcher verwaltet

Initialisieren Sie zunächst ein Subs-Array zum Speichern von Abhängigkeiten, also Beobachtern. Wer von diesen Daten abhängig ist, befindet sich in diesem Array. Definieren Sie dann mehrere Methoden zum Hinzufügen, Löschen, Melden von Aktualisierungen usw.

Darüber hinaus verfügt es über ein statisches Attributziel, nämlich einen globalen Watcher. Dies bedeutet auch, dass gleichzeitig nur ein globaler Watcher existieren kann.

sei uid = 0
exportiere Standardklasse Dep {
  statisches Ziel: ?Watcher;
  ID: Nummer;
  subs: Array<Watcher>;
  Konstruktor () {
    diese.id = uid++
    dies.subs = []
  }
  // Einen Beobachter hinzufügen addSub (sub: Watcher) {
    dies.subs.push(sub)
  }
  // Den Beobachter entfernen removeSub (sub: Watcher) {
    entfernen(diese.subs, sub)
  }
  abhängen() {
    wenn (Dep.target) {
      //Rufen Sie die addDep-Funktion von Watcher auf: Dep.target.addDep(this)
    }
  }
  // Updates verteilen (im nächsten Abschnitt beschrieben)
  benachrichtigen () {
    ...
  }
}
// Es wird immer nur ein Beobachter verwendet. Weisen Sie dem Beobachter Dep.target = null zu.
const ZielStack = []

Exportfunktion pushTarget (Ziel: ?Watcher) {
  ZielStack.push(Ziel)
  Dep.Ziel = Ziel
}

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

Beobachter

Quellcode-Adresse: src/core/observer/watcher.js

Watcher ist auch eine Klasse, auch Beobachter (Abonnent) genannt. Die hier ausgeführte Arbeit ist ziemlich kompliziert und verbindet auch Rendering und Kompilierung.

Schauen wir uns zuerst den Quellcode an und gehen wir dann den gesamten Prozess der Abhängigkeitssammlung durch.

sei uid = 0
exportiere Standardklasse Watcher {
  ...
  Konstruktor (
    vm: Komponente,
    expOrFn: Zeichenfolge | Funktion,
    cb: Funktion,
    Optionen?: ?Objekt,
    istRenderWatcher?: Boolesch
  ) {
    dies.vm = vm
    wenn (istRenderWatcher) {
      vm._watcher = dies
    }
    vm._watchers.push(dies)
    // Array von Dep-Instanzen, die von der Watcher-Instanz gehalten werden this.deps = []
    dies.newDeps = []
    this.depIds = neues Set()
    this.newDepIds = neues Set()
    dieser.Wert = dies.lazy
      ? nicht definiert
      : dies.get()
    wenn (Typ von expOrFn === 'Funktion') {
      this.getter = expOrFn
    } anders {
      this.getter = parsePath(expOrFn)
    }
  }
  erhalten () 
    // Diese Funktion wird zum Zwischenspeichern von Watcher verwendet
    // Da die Komponente verschachtelte Komponenten enthält, muss der Watcher der übergeordneten Komponente wiederhergestellt werden
    drückeZiel(dieses)
    let-Wert
    const vm = diese.vm
    versuchen {
      // Rufe die Rückruffunktion, also upcateComponent, auf, um das Objekt auszuwerten, das eine bidirektionale Bindung benötigt, und löse dadurch die Abhängigkeitssammlung aus. Wert = this.getter.call(vm, vm)
    } fangen (e) {
      ...
    Endlich
      // Gründliche Überwachung if (this.deep) {
        traverse(Wert)
      }
      // Watcher wiederherstellen
      popZiel()
      // Bereinigen Sie unnötige Abhängigkeiten this.cleanupDeps()
    }
    Rückgabewert
  }
  // Rufe addDep (dep: Dep) auf, wenn die Abhängigkeitssammlung {
    const id = dep.id
    wenn (!this.newDepIds.has(id)) {
      this.newDepIds.add(ich würde)
      dies.newDeps.push(dep)
      wenn (!this.depIds.has(id)) {
        // Schiebe den aktuellen Watcher in das Array dep.addSub(this)
      }
    }
  }
  // Unnötige Abhängigkeiten bereinigen (unten)
  bereinigungDeps() {
    ...
  }
  // Wird aufgerufen, wenn Updates versendet werden (unten)
  aktualisieren () {
    ...
  }
  // Führe den Watcher-Callback aus run () {
    ...
  }
  abhängen() {
    sei i = this.deps.length
    während (i--) {
      dies.deps[i].depend()
    }
  }
}

Auffüllen:

Warum kann die in unserer eigenen Komponente geschriebene Uhr automatisch die beiden Parameter „Neuer Wert“ und „Alter Wert“ erhalten?

Das heißt, der Rückruf wird in watcher.run() ausgeführt und der neue und der alte Wert werden übergeben.

Warum zwei Dep-Instanz-Arrays initialisieren?

Da Vue datengesteuert ist, werden die Daten bei jeder Änderung neu gerendert, d. h. die Methode vm.render() wird erneut ausgeführt und der Getter wird erneut ausgelöst. Daher werden zwei Arrays zur Darstellung verwendet: das neu hinzugefügte Dep-Instanzarray newDeps und das zuletzt hinzugefügte Instanzarray deps

Prozess der Abhängigkeitssammlung

Beim ersten Rendern und Mounten gibt es eine solche Logik

mountComponent Quellcodeadresse: src/core/instance/lifecycle.js – Zeile 141

Exportfunktion mountComponent (...): Komponente {
  //Rufen Sie die Lebenszyklus-Hook-Funktion callHook(vm, 'beforeMount') auf.
  let updateComponent
  updateComponent = () => {
    // Rufen Sie _update auf, um den von render zurückgegebenen virtuellen DOM in den realen DOM zu patchen (d. h. zu differenzieren). Dies ist das erste Rendering vm._update(vm._render(), hydrating)
  }
  // Richten Sie einen Beobachter für die aktuelle Komponenteninstanz ein, um die von der Funktion updateComponent erhaltenen Daten zu überwachen. Im Folgenden finden Sie eine Einführung: new Watcher(vm, updateComponent, noop, {
    // Wenn ein Update ausgelöst wird, wird before() vor dem Update aufgerufen {
      // Bestimmen Sie, ob das DOM gemountet ist, d. h., es wird beim ersten Rendern und Unmounten nicht ausgeführt, wenn (vm._isMounted && !vm._isDestroyed) {
        //Rufen Sie die Lifecycle-Hook-Funktion callHook(vm, 'beforeUpdate') auf.
      }
    }
  }, true /* istRenderWatcher */)
  // Es gibt keinen alten vnode, was darauf hinweist, dass es sich um das erste Rendering handelt, wenn (vm.$vnode == null) {
    vm._isMounted = wahr
    //Rufen Sie die Lebenszyklus-Hook-Funktion callHook(vm, 'mounted') auf.
  }
  VM zurückgeben
}

Sammlung von Abhängigkeiten:

  • Vor dem Mounten wird ein Rendering-Watcher instantiiert und die Methode this.get() wird beim Aufrufen des Watcher-Konstruktors ausgeführt.
  • Anschließend wird pushTarget(this) ausgeführt, das Dep.target dem aktuellen Rendering-Watcher zuweist und auf den Stack schiebt (zur Wiederherstellung).
  • Führen Sie dann this.getter.call(vm, vm) aus. Dies ist die obige Funktion updateComponent(), die vm._update(vm._render(), hydrating) ausführt.
  • Führen Sie dann vm._render() aus, um einen Rendering-Vnode zu generieren. Während dieses Vorgangs wird auf die Daten in der VM zugegriffen, wodurch der Getter des Datenobjekts ausgelöst wird.
  • Jeder Objektwert-Getter hat ein Dep. Wenn der Getter ausgelöst wird, wird die Methode dep.depend() aufgerufen, die auch Dep.target.addDep(this) ausführt.
  • Dann werden hier einige Beurteilungen vorgenommen, um sicherzustellen, dass dieselben Daten nicht mehrmals hinzugefügt werden, und dann werden die qualifizierten Daten in Unterelemente übertragen. Zu diesem Zeitpunkt ist die Sammlung von Abhängigkeiten abgeschlossen, aber noch nicht beendet. Wenn es sich um ein Objekt handelt, wird der Getter aller Unterelemente rekursiv ausgelöst und der Dep.target-Status muss wiederhergestellt werden.

Entfernen eines Abonnements

Das Entfernen eines Abonnements erfolgt durch den Aufruf der Methode cleanupDeps(). Wenn beispielsweise v-if in der Vorlage vorhanden ist, sammeln wir die Abhängigkeiten in Vorlage a, die die Bedingungen erfüllen. Bei einer Änderung der Bedingung wird Vorlage B angezeigt und Vorlage A ausgeblendet. Zu diesem Zeitpunkt müssen Sie die Abhängigkeit von einem

Die wichtigsten Dinge, die hier getan werden, sind:

  • Durchlaufen Sie zuerst das zuletzt hinzugefügte Instanzarray deps und entfernen Sie das Abonnement von Watcher im Array dep.subs
  • Dann tauschen Sie newDepIds mit depIds, newDeps mit deps
  • Löschen Sie dann newDepIds und newDeps
// Unnötige Abhängigkeiten bereinigen cleanupDeps () {
    sei i = this.deps.length
    während (i--) {
      const dep = dies.deps[i]
      wenn (!this.newDepIds.has(dep.id)) {
        dep.removeSub(dies)
      }
    }
    lass tmp = this.depIds
    diese.depIds = diese.newDepIds
    this.newDepIds = tmp
    dies.newDepIds.clear()
    tmp = dies.deps
    dies.deps = dies.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

Updates verteilen

benachrichtigen()

Wenn der Setter ausgelöst wird, wird dep.notify() aufgerufen, um alle Abonnenten zu benachrichtigen und Updates zu verteilen.

benachrichtigen () {
    const subs = this.subs.slice()
    wenn (Prozess.Umgebung.NODE_ENV !== 'Produktion' && !config.async) {
      // Wenn nicht asynchron, ist eine Sortierung erforderlich, um eine korrekte Auslösung sicherzustellen. subs.sort((a, b) => a.id - b.id)
    }
    // Alle Watcher-Instanz-Arrays durchlaufen for (let i = 0, l = subs.length; i < l; i++) {
      // Update auslösen subs[i].update()
    }
  }

aktualisieren()

Wird aufgerufen, wenn ein Update ausgelöst wird

  aktualisieren () {
    wenn (dies.lazy) {
      dies.schmutzig = wahr
    } sonst wenn (diese.sync) {
      dies.laufen()
    } anders {
      //Aktualisierungen der Komponentendaten werden hier angezeigt queueWatcher(this)
    }
  }

Warteschlangenwächter()

Quellcodeadresse: src/core/observer/scheduler.js – Zeile 164

Dies ist eine Warteschlange und auch ein Optimierungspunkt, wenn Vue Updates verteilt. Das heißt, der Watcher-Callback wird nicht bei jeder Datenänderung ausgelöst, sondern alle diese Watcher werden zu einer Warteschlange hinzugefügt und dann nach dem nächsten Tick ausgeführt.

Dies überschneidet sich mit der Logik von flushSchedulerQueue() im nächsten Abschnitt, daher müssen wir sie zusammen verstehen.

Die Hauptaufgaben sind:

  • Verwenden Sie zuerst das has-Objekt, um die ID zu finden, um sicherzustellen, dass derselbe Beobachter nur einmal pusht
  • Wenn während der Ausführung des Watchers ein neuer Watcher eingefügt wird, kommt er hierher und sucht dann von hinten nach vorne, um die erste Position zu finden, an der die einzufügende ID größer ist als die ID in der aktuellen Warteschlange, und fügt sie in die Warteschlange ein, sodass sich die Länge der Warteschlange ändert
  • Schließlich stellen wir durch das Warten sicher, dass nextTick nur einmal aufgerufen wird.
Exportfunktion queueWatcher (Watcher: Watcher) {
  // Holen Sie sich die Watcher-ID
  const id = watcher.id
  // Prüfen, ob der Watcher mit der aktuellen ID gepusht wurde if (has[id] == null) {
    hat[id] = wahr
    wenn (!spülen) {
      //Geben Sie hier zuerst queue.push(watcher) ein
    } anders {
      // Wenn beim Ausführen der folgenden FlushSchedulerQueue ein neu verteiltes Update vorliegt, wird es hier eingegeben und ein neuer Watcher eingefügt. Das Folgende ist eine Einführung: let i = queue.length - 1
      während (i > Index && Warteschlange[i].id > Watcher.id) {
        ich--
      }
      Warteschlange.splice(i + 1, 0, Beobachter)
    }
    // Wird hier zuerst eingetragen if (!waiting) {
      warten = wahr
      wenn (Prozess.Umgebung.NODE_ENV !== 'Produktion' && !config.async) {
        flushSchedulerQueue()
        zurückkehren
      }
      // Da jedes Versenden von Updates zu Rendering führt, setzen Sie alle Beobachter in nextTick und rufen Sie nextTick(flushSchedulerQueue) auf.
    }
  }
}

flushSchedulerQueue()

Quellcodeadresse: src/core/observer/scheduler.js – Zeile 71

Die wichtigsten Dinge, die hier getan werden, sind:

  • Sortieren Sie zuerst die Warteschlange. Es gibt drei Sortierbedingungen. Siehe die Kommentare.
  • Durchlaufen Sie dann die Warteschlange und führen Sie den entsprechenden watcher.run() aus. Es ist zu beachten, dass die Warteschlangenlänge bei jedem Durchlauf ausgewertet wird, da nach dem Durchlauf wahrscheinlich ein neuer Watcher hinzugefügt wird und der obige QueueWatcher anschließend erneut ausgeführt wird.
Funktion flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  Spülen = wahr
  lass Beobachter, id

  // Nach ID sortieren, dabei gelten folgende Bedingungen // 1. Komponentenaktualisierungen müssen in der Reihenfolge vom übergeordneten zum untergeordneten Element erfolgen, da auch beim Erstellungsprozess zuerst das übergeordnete Element und dann das untergeordnete Element erfolgt // 2. Der Watcher, den wir in die Komponente schreiben, hat Vorrang vor dem Rendering-Watcher
  // 3. Wenn eine Komponente zerstört wird, während der Watcher der übergeordneten Komponente läuft, überspringen Sie diesen Watcher
  Warteschlange.sortieren((a, b) => a.id - b.id)

  // Die Warteschlangenlänge nicht im Cache speichern, da sich die Warteschlangenlänge während der Durchquerung ändern kann for (index = 0; index < queue.length; index++) {
    Beobachter = Warteschlange[Index]
    wenn (watcher.before) {
      //Führen Sie die Lebenszyklus-Hook-Funktion watcher.before() für „beforeUpdate“ aus.
    }
    id = Beobachter.id
    hat[id] = null
    // Führe die Callback-Funktion der Uhr aus, die wir in die Komponente geschrieben haben, und rendere die Komponente watcher.run()
    // Überprüfen und stoppen Sie die Schleifenaktualisierung. Wenn beispielsweise das Objekt während des Watcher-Prozesses neu zugewiesen wird, tritt eine Endlosschleife auf, wenn (process.env.NODE_ENV !== 'production' && has[id] != null) {
      kreisförmig[id] = (kreisförmig[id] || 0) + 1
      wenn (rundschreiben[id] > MAX_UPDATE_COUNT) {
        warnen(`Endlosschleife`)
        brechen
      }
    }
  }
  // Vor dem Zurücksetzen des Status eine Kopie der Warteschlange aufbewahren const activatedQueue = activatedChildren.slice()
  const aktualisierteQueue = queue.slice()
  resetSchedulerState()
  // Aufrufkomponenten-Aktivierungs-Hook aktiviert
  rufActivatedHooks auf(activatedQueue)
  // Aufruf des Komponenten-Update-Hooks aktualisiert
  rufUpdatedHooks auf(aktualisierteWarteschlange)
}

aktualisiert()

Schließlich können wir aktualisieren. Jeder kennt updated, die Lebenszyklus-Hook-Funktion.

Wenn callUpdatedHooks() oben aufgerufen wird, wird es hier eintreten und aktualisiert ausführen

Funktion callUpdatedHooks (Warteschlange) {
  sei i = Warteschlangenlänge
  während (i--) {
    const watcher = Warteschlange[i]
    const vm = watcher.vm
    wenn (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'aktualisiert')
    }
  }
}

An diesem Punkt wurde der Quellcode des responsiven Prinzipprozesses von Vue2 grundsätzlich analysiert. Als Nächstes werde ich die Mängel des obigen Prozesses vorstellen.

definierenEigenschaftsmängel und Lösungen

Es gibt immer noch einige Probleme bei der Verwendung von Object.defineProperty zur Implementierung responsiver Objekte

  • Wenn Sie beispielsweise einem Objekt eine neue Eigenschaft hinzufügen, kann der Setter nicht ausgelöst werden.
  • Beispielsweise können Änderungen an Array-Elementen nicht erkannt werden

Und für diese Probleme gibt es in Vue2 auch entsprechende Lösungen

Vue.set()

Wenn Sie einem Objekt neue responsive Eigenschaften hinzufügen, können Sie eine globale API verwenden, die Vue.set()-Methode

Quellcodeadresse: src/core/observer/index.js – Zeile 201

Die Set-Methode akzeptiert drei Parameter:

  • Ziel: Array oder normales Objekt
  • Schlüssel: stellt den Array-Index oder den Objektschlüsselnamen dar
  • val: stellt den neuen Wert dar, der ersetzt werden soll

Die wichtigsten Dinge, die hier getan werden, sind:

  • Bestimmen Sie zunächst, ob es sich um ein Array handelt und der Index zulässig ist, und ersetzen Sie ihn dann direkt durch den neu geschriebenen Spleiß.
  • Wenn es sich um ein Objekt handelt und der Schlüssel im Ziel vorhanden ist, ersetzen Sie den Wert
  • Wenn kein __ob__ vorhanden ist, bedeutet dies, dass es sich nicht um ein responsives Objekt handelt und der Wert direkt zugewiesen und zurückgegeben wird
  • Machen Sie die neue Immobilie schließlich responsive und versenden Sie Updates
Exportfunktionssatz (Ziel: Array<beliebig> | Objekt, Schlüssel: beliebig, Wert: beliebig): beliebig {
  wenn (Prozess.env.NODE_ENV !== 'Produktion' &&
    (istUndef(Ziel) || istPrimitive(Ziel))
  ) {
    warnen(`Reaktive Eigenschaft kann nicht auf undefinierten, Null- oder primitiven Wert gesetzt werden: ${(target: any)}`)
  }
  // Wenn es ein Array ist und der Index gültig ist, if (Array.isArray(target) && isValidArrayIndex(key)) {
    Ziellänge = Math.max(Ziellänge, Schlüssel)
    // Verwenden Sie splice, um direkt zu ersetzen. Beachten Sie, dass splice hier nicht nativ ist und daher erkannt werden kann. Weitere Einzelheiten finden Sie unten unter target.splice(key, 1, val)
    Rückgabewert
  }
  // Das bedeutet, es handelt sich um ein Objekt // Wenn der Schlüssel im Ziel vorhanden ist, wird er direkt zugewiesen und kann auch überwacht werden, wenn (Schlüssel in Ziel && !(Schlüssel in Objekt.Prototyp)) {
    Ziel[Schlüssel] = Wert
    Rückgabewert
  }
  //Ziel abrufen.__ob__
  const ob = (Ziel: beliebig).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'Produktion' && warn(
      'Vermeiden Sie das Hinzufügen reaktiver Eigenschaften zu einer Vue-Instanz oder ihren Stamm-$data' +
      „Zur Laufzeit – deklarieren Sie es im Voraus in der Datenoption.“
    )
    Rückgabewert
  }
  // Wie in Observer eingeführt, bedeutet das Fehlen dieses Attributs, dass es sich nicht um ein responsives Objekt handelt, if (!ob) {
    Ziel[Schlüssel] = Wert
    Rückgabewert
  }
  // Dann machen Sie das neu hinzugefügte Attribut responsive defineReactive(ob.value, key, val)
  // Updates manuell versenden ob.dep.notify()
  Rückgabewert
}

Überschreiben von Array-Methoden

Quellcodeadresse: src/core/observer/array.js

Die wichtigsten Dinge, die hier getan werden, sind:

  • Speichern Sie eine Liste der Methoden, die das Array ändern
  • Wenn Sie eine Methode in der Liste ausführen, z. B. Push, speichern Sie zuerst den ursprünglichen Push, führen Sie dann eine Reaktionsverarbeitung durch und führen Sie dann diese Methode aus
// Den Prototyp des Arrays abrufen const arrayProto = Array.prototype
// Ein Objekt erstellen, das den Array-Prototyp erbt. export const arrayMethods = Object.create(arrayProto)
// Ändert die Methodenliste des ursprünglichen Arrays const methodsToPatch = [
  'drücken',
  'Pop',
  'Schicht',
  'aufheben',
  'spleißen',
  'Sortieren',
  'umkehren'
]
// Array-Ereignismethoden neu schreibenToPatch.forEach(function (method) {
  //Das ursprüngliche Ereignis speichern const original = arrayProto[method]
  // Ein responsives Objekt erstellen def(arrayMethods, method, function mutator (...args) {
    const Ergebnis = original.anwenden(dies, Argumente)
    const ob = this.__ob__
    eingefügt lassen
    Schalter (Methode) {
      Fall 'push':
      Fall 'Unshift':
        eingefügt = Argumente
        brechen
      Fall 'Spleißen':
        eingefügt = args.slice(2)
        brechen
    }
    if (eingefügt) ob.observeArray(eingefügt)
    // Updates verteilen ob.dep.notify()
    //Nach Abschluss der erforderlichen Verarbeitung das ursprüngliche Ereignis ausführen und das Ergebnis zurückgeben
  })
})

Zusammenfassen

Dies ist das Ende dieses Artikels über die Interpretation des Quellcodes nach dem responsiven Prinzip durch Vue. Weitere relevante Inhalte zum Quellcode nach dem responsiven Prinzip von Vue 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:
  • Detaillierte Erklärung des VUE-Reaktionsprinzips
  • Beispiel für die Implementierung des zugrunde liegenden Codes zur Simulation responsiver Prinzipien durch Vue
  • Detaillierte Analyse des Reaktionsprinzips und der bidirektionalen Daten von Vue
  • Eine kurze Analyse des Reaktionsprinzips und der Unterschiede von Vue2.0/3.0
  • Detaillierte Erläuterung des Datenreaktionsprinzips von Vue
  • Detaillierte Analyse des Vue-Reaktionsprinzips
  • Detaillierte Erklärung des Responsive-Prinzips von Vue3

<<:  So zeigen Sie MySql-Indizes an und optimieren sie

>>:  Lösung für das Problem, dass Docker CMD/ENTRYPOINT das sh-Skript ausführt: nicht gefunden/run.sh:

Artikel empfehlen

Detaillierte Erläuterung der FTP-Umgebungskonfigurationslösung (vsftpd)

1. Installieren Sie die vsftpd-Komponente Install...

Webdesign-Tutorial (4): Über Materialien und Ausdrücke

<br />Vorheriges Webdesign-Tutorial: Webdesi...

HTML-Tabellen-Markup-Tutorial (43): VALIGN-Attribut der Tabellenüberschrift

In vertikaler Richtung können Sie die Ausrichtung...

So überprüfen Sie, ob MySQL erfolgreich installiert wurde

Nachdem MySQL installiert wurde, können Sie in ei...

Beispielcode zum Konvertieren des Mysql-Abfrageergebnissatzes in JSON-Daten

Mysql konvertiert Abfrageergebnissatz in JSON-Dat...

SQL-Implementierung von LeetCode (184. Das höchste Gehalt der Abteilung)

[LeetCode] 184. Abteilung Höchstes Gehalt Die Mit...

Analyse und Lösung des durch Chrome 73 verursachten Flex-Layout-Zusammenbruchs

Phänomen Es gibt mehrere verschachtelte Flex-Stru...

Wie man die Idee von Vue nutzt, um einen Speicher zu kapseln

Inhaltsverzeichnis Hintergrund Funktion Zweck Ide...

Tiefgreifendes Verständnis der Matching-Logik von Server und Standort in Nginx

Server-Abgleichlogik Wenn Nginx entscheidet, in w...

Implementierung der Docker-Container-Verbindung und -Kommunikation

Die Portzuordnung ist nicht die einzige Möglichke...

Anfänger lernen einige HTML-Tags (1)

Anfänger können HTML lernen, indem sie einige HTM...

Tipps, wie Sie aus Pixeln umfassende Markenerlebnisse machen

Herausgeber: In diesem Artikel wird die Rolle erö...

Kennen Sie die häufigsten MySQL-Designfehler?

Dank der Entwicklung des Internets können wir die...

Warum MySQL-Datenbanken NULL so weit wie möglich vermeiden

Viele Tabellen in MySQL enthalten Spalten, die NU...