Heim >Web-Frontend >js-Tutorial >Was ist das Implementierungsprinzip der Berechnung in Vue?

Was ist das Implementierungsprinzip der Berechnung in Vue?

不言
不言Original
2018-09-14 16:10:063879Durchsuche
Der Inhalt dieses Artikels befasst sich mit dem Implementierungsprinzip der Berechnung in Vue? Es hat einen gewissen Referenzwert. Freunde in Not können sich darauf beziehen. Ich hoffe, es wird Ihnen hilfreich sein.

Obwohl der aktuelle Technologie-Stack von Vue auf React übertragen wurde, ist die tatsächliche Erfahrung bei der Entwicklung mehrerer Projekte mit Vue immer noch sehr angenehm. Die Vue-Dokumentation ist klar und standardisiert, das API-Design ist einfach und effizient und benutzerfreundlich Für Front-End-Entwickler ist der Einstieg schnell und ich persönlich denke sogar, dass die Verwendung von Vue in vielen Szenarien effizienter ist als die React-Entwicklung. Ich habe den Quellcode von Vue schon einmal studiert, aber ich habe ihn nie zusammengefasst Ich werde hier eine technische Zusammenfassung erstellen und mein Verständnis von Vue vertiefen. Worüber ich heute schreiben werde, ist das Implementierungsprinzip von computed, einer der am häufigsten verwendeten APIs in Vue.

Grundlegende Einführung

Es gibt nicht viel zu sagen, ein einfaches Beispiel lautet wie folgt:

<div>
    <p>{{fullName}}</p>
</div>
rrree

In Vue müssen wir nicht {{this.firstName + ' ' + this.lastName}} direkt in der Vorlage berechnen, denn in der Vorlage Wenn zu viel deklarative Logik in die Vorlage eingefügt wird, wird die Vorlage selbst zu schwer. Insbesondere wenn eine große Anzahl komplexer logischer Ausdrücke zum Verarbeiten von Daten auf der Seite verwendet wird, hat dies große Auswirkungen auf die Wartbarkeit der Seite , und computed Die ursprüngliche Entwurfsabsicht besteht darin, solche Probleme zu lösen.

Listener vergleichenwatch

Wenn wir computed verwenden, vergleichen wir es natürlich oft mit einer anderen API in Vue, nämlich dem Listener watch, denn in a Sicher Sie sind in mancher Hinsicht konsistent und basieren auf dem Abhängigkeitsverfolgungsmechanismus von Vue. Wenn sich bestimmte Abhängigkeitsdaten ändern, werden alle zugehörigen Daten oder Funktionen, die von diesen Daten abhängen, automatisch geändert oder aufgerufen.

Während berechnete Eigenschaften in den meisten Fällen besser geeignet sind, ist manchmal ein benutzerdefinierter Listener erforderlich. Aus diesem Grund bietet Vue über die Option watch eine allgemeinere Möglichkeit, auf Datenänderungen zu reagieren. Dieser Ansatz ist am nützlichsten, wenn Sie bei Datenänderungen asynchrone oder teure Vorgänge ausführen müssen.

Aus der Erklärung von watch in der offiziellen Vue-Dokumentation können wir verstehen, dass die Verwendung der Option watch es uns ermöglicht, asynchrone Vorgänge (Zugriff auf eine API) oder Hochleistungsvorgänge auszuführen und die Häufigkeit unserer Ausführung zu begrenzen Operation, und bevor wir das Endergebnis erhalten, richten wir Zwischenzustände ein, was berechnete Eigenschaften nicht können.

Im Folgenden sind außerdem einige zusätzliche Punkte zu den Unterschieden zwischen computed und watch zusammengefasst:

  1. computed ist zu berechnen ein neues Attribut und mounten Sie das Attribut in der VM (Vue-Instanz), und watch dient dazu, die Daten zu überwachen, die bereits vorhanden sind und auf vm gemountet wurden, sodass mit watch auch das berechnete Attribut von Änderung (andere umfassen computed, data) props

  2. ist im Wesentlichen ein verzögert bewerteter Beobachter mit Cachefähigkeit. Nur wenn sich die Abhängigkeit ändert, wird der erste Der neue Wert wird erst berechnet, wenn mehrmals auf das Attribut computed zugegriffen wird, und computed ruft die Ausführungsfunktion watch

  3. auf, wenn sich die Daten ändern.

    Eins Daten werden von mehreren Daten beeinflusst, und computed gilt für einzelne Daten, die sich auf mehrere Daten auswirken. watch

Oben verstehen wir einige der Unterschiede zwischen

und computed Zwischen den Nutzungsszenarien sind die beiden natürlich manchmal nicht so klar und streng. Am Ende müssen sie dennoch speziell für verschiedene Unternehmen analysiert werden. watch

Prinzipielle Analyse

Um auf das Thema des Artikels zurückzukommen

: Um den internen Mechanismus berechneter Eigenschaften besser zu verstehen, lassen Sie uns ihn Schritt für Schritt untersuchen Vue-Quellcode Das Implementierungsprinzip. computed

Bevor wir den

Quellcode analysieren, müssen wir zunächst ein grundlegendes Verständnis des reaktionsfähigen Systems von Vue haben. Vue nennt es ein nicht aufdringliches reaktionsfähiges System Wenn Sie darauf klicken, wird die Ansicht automatisch aktualisiert. computed

Was ist das Implementierungsprinzip der Berechnung in Vue?Wenn Sie ein normales JavaScript-Objekt an die Option

einer Vue-Instanz übergeben, durchläuft Vue alle Eigenschaften dieses Objekts und verwendet data Konvertieren Sie alle diese Eigenschaften in Object.defineProperty. Diese getter/setter sind für den Benutzer unsichtbar, aber intern ermöglichen sie Vue, Abhängigkeiten zu verfolgen und Änderungen zu benachrichtigen, wenn auf Eigenschaften zugegriffen und diese geändert werden. Jede Komponenteninstanz verfügt über ein entsprechendes Instanzobjekt getter/setter zeichnet die Eigenschaften beim Rendern der Komponente als Abhängigkeiten auf. Wenn später watcher der Abhängigkeit aufgerufen wird, wird setter zur Neuberechnung aufgefordert, wodurch die zugehörigen Komponenten aktualisiert werden. watcherDas Vue-Antwortsystem hat drei Kernpunkte:

, observe, watcher: dep

  1. observe:遍历 data 中的属性,使用 Object.defineProperty 的 get/set 方法对其进行数据劫持;

  2. dep:每个属性拥有自己的消息订阅器 dep,用于存放所有订阅了该属性的观察者对象;

  3. watcher:观察者(对象),通过 dep 实现对响应属性的监听,监听到结果后,主动触发自己的回调进行响应。

对响应式系统有一个初步了解后,我们再来分析计算属性。
首先我们找到计算属性的初始化是在 src/core/instance/state.js 文件中的 initState 函数中完成的

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // computed初始化
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

调用了 initComputed 函数(其前后也分别初始化了 initDatainitWatch )并传入两个参数 vm 实例和 opt.computed 开发者定义的 computed 选项,转到 initComputed 函数:

const computedWatcherOptions = { computed: true }

function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        'Getter is missing for computed property "${key}".',
        vm
      )
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn('The computed property "${key}" is already defined in data.', vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn('The computed property "${key}" is already defined as a prop.', vm)
      }
    }
  }
}

从这段代码开始我们观察这几部分:

  1. 获取计算属性的定义 userDefgetter 求值函数

    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get

    定义一个计算属性有两种写法,一种是直接跟一个函数,另一种是添加 setget 方法的对象形式,所以这里首先获取计算属性的定义 userDef,再根据 userDef 的类型获取相应的 getter 求值函数。

  2. 计算属性的观察者 watcher 和消息订阅器 dep

    watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
    )

    这里的 watchers 也就是 vm._computedWatchers 对象的引用,存放了每个计算属性的观察者 watcher 实例(注:后文中提到的“计算属性的观察者”、“订阅者”和 watcher 均指代同一个意思但注意和 Watcher 构造函数区分),Watcher 构造函数在实例化时传入了 4 个参数:vm 实例、getter 求值函数、noop 空函数、computedWatcherOptions 常量对象(在这里提供给 Watcher 一个标识 {computed:true} 项,表明这是一个计算属性而不是非计算属性的观察者,我们来到 Watcher 构造函数的定义:

    class Watcher {
      constructor (
        vm: Component,
        expOrFn: string | Function,
        cb: Function,
        options?: ?Object,
        isRenderWatcher?: boolean
      ) {
        if (options) {
          this.computed = !!options.computed
        } 
    
        if (this.computed) {
          this.value = undefined
          this.dep = new Dep()
        } else {
          this.value = this.get()
        }
      }
      
      get () {
        pushTarget(this)
        let value
        const vm = this.vm
        try {
          value = this.getter.call(vm, vm)
        } catch (e) {
          
        } finally {
          popTarget()
        }
        return value
      }
      
      update () {
        if (this.computed) {
          if (this.dep.subs.length === 0) {
            this.dirty = true
          } else {
            this.getAndInvoke(() => {
              this.dep.notify()
            })
          }
        } else if (this.sync) {
          this.run()
        } else {
          queueWatcher(this)
        }
      }
    
      evaluate () {
        if (this.dirty) {
          this.value = this.get()
          this.dirty = false
        }
        return this.value
      }
    
      depend () {
        if (this.dep && Dep.target) {
          this.dep.depend()
        }
      }
    }

    为了简洁突出重点,这里我手动去掉了我们暂时不需要关心的代码片段。
    观察 Watcherconstructor ,结合刚才讲到的 new Watcher 传入的第四个参数 {computed:true} 知道,对于计算属性而言 watcher 会执行 if 条件成立的代码 this.dep = new Dep(),而 dep 也就是创建了该属性的消息订阅器。

    export default class Dep {
      static target: ?Watcher;
      subs: Array<watcher>;
    
      constructor () {
        this.id = uid++
        this.subs = []
      }
    
      addSub (sub: Watcher) {
        this.subs.push(sub)
      }
    
      depend () {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }
    
      notify () {
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i <p><code>Dep</code> 同样精简了部分代码,我们观察 <code>Watcher</code> 和 <code>Dep</code> 的关系,用一句话总结</p>
    <blockquote>
    <code>watcher</code> 中实例化了 <code>dep</code> 并向 <code>dep.subs</code> 中添加了订阅者,<code>dep</code> 通过 <code>notify</code> 遍历了 <code>dep.subs</code> 通知每个 <code>watcher</code> 更新。</blockquote></watcher>
  3. defineComputed 定义计算属性

    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn('The computed property "${key}" is already defined in data.', vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn('The computed property "${key}" is already defined as a prop.', vm)
      }
    }

    因为 computed 属性是直接挂载到实例对象中的,所以在定义之前需要判断对象中是否已经存在重名的属性,defineComputed 传入了三个参数:vm 实例、计算属性的 key 以及 userDef 计算属性的定义(对象或函数)。
    然后继续找到 defineComputed 定义处:

    export function defineComputed (
      target: any,
      key: string,
      userDef: Object | Function
    ) {
      const shouldCache = !isServerRendering()
      if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = shouldCache
          ? createComputedGetter(key)
          : userDef
        sharedPropertyDefinition.set = noop
      } else {
        sharedPropertyDefinition.get = userDef.get
          ? shouldCache && userDef.cache !== false
            ? createComputedGetter(key)
            : userDef.get
          : noop
        sharedPropertyDefinition.set = userDef.set
          ? userDef.set
          : noop
      }
      if (process.env.NODE_ENV !== 'production' &&
          sharedPropertyDefinition.set === noop) {
        sharedPropertyDefinition.set = function () {
          warn(
            'Computed property "${key}" was assigned to but it has no setter.',
            this
          )
        }
      }
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }

    在这段代码的最后调用了原生 Object.defineProperty 方法,其中传入的第三个参数是属性描述符sharedPropertyDefinition,初始化为:

    const sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    }

    随后根据 Object.defineProperty 前面的代码可以看到 sharedPropertyDefinitionget/set 方法在经过 userDef 和  shouldCache 等多重判断后被重写,当非服务端渲染时,sharedPropertyDefinitionget 函数也就是 createComputedGetter(key) 的结果,我们找到 createComputedGetter 函数调用结果并最终改写  sharedPropertyDefinition 大致呈现如下:

    sharedPropertyDefinition = {
        enumerable: true,
        configurable: true,
        get: function computedGetter () {
            const watcher = this._computedWatchers && this._computedWatchers[key]
            if (watcher) {
                watcher.depend()
                return watcher.evaluate()
            }
        },
        set: userDef.set || noop
    }

    当计算属性被调用时便会执行 get 访问函数,从而关联上观察者对象 watcher 然后执行 wather.depend() 收集依赖和 watcher.evaluate() 计算求值。

Nachdem wir alle Schritte analysiert haben, fassen wir den gesamten Prozess zusammen:

  1. Wenn die Komponente initialisiert wird, erstellen computed und data ihre eigenen Das Antwortsystem Observer durchläuft jede Attributeinstellung in data get/set Datenabfang

  2. Initialisierung computed ruft die initComputed Funktion

    1. Registrieren Sie eine

      -Instanz und instanziieren Sie darin einen watcher-Nachrichtenabonnenten für nachfolgende Sammlungsabhängigkeiten (z. B. Dep der Rendering-Funktion oder andere watcher, die Änderungen in der berechneten Eigenschaft beobachten). ) watcher

    2. löst die

      -Zugriffsfunktion Object.definePropertyget

    3. seines
    4. aus, um die eigene Methode

      aufzurufen. Andere hinzufügen Attribute zu watcher.depend()depsubswatcher

      des Nachrichtenabonnenten
    5. und rufen Sie die
    6. -Methode von

      auf (die wiederum die watcher-Methode von evaluate aufruft) Machen Sie sich zum Abonnent anderer watcher-Nachrichtenabonnenten. Weisen Sie dann get zu watcher zu und führen Sie dann die watcher-Auswertungsfunktion aus (z. B. von Dep.target, oder andere getter), ihre data-Zugriffsfunktion wird auch ausgelöst, um die berechnete Eigenschaft props zum Nachrichtenabonnenten computed der Eigenschaft get in der Auswertungsfunktion hinzuzufügen, wenn diese Vorgänge abgeschlossen sind Schließen Sie schließlich watcher, weisen Sie es watcher zu und geben Sie das Ergebnis der Bewertungsfunktion zurück. depDep.targetnull

    7. Wenn sich ein bestimmtes Attribut ändert, lösen Sie die Abfangfunktion
    aus und rufen Sie dann die Methode
  3. des eigenen Nachrichtenabonnenten

    auf, um die aktuelle Speichern Sie das set-Array aller Abonnenten dep und rufen Sie nacheinander die notify-Methode von dep auf, um die Antwortaktualisierung abzuschließen. wathcersubswatcherupdateVerwandte Empfehlungen:

Javascript - Implementierungsprinzip von Ticket Brusher

Thinkphp-Controller-Prinzip von Implementierung des display()-Schritts

Das obige ist der detaillierte Inhalt vonWas ist das Implementierungsprinzip der Berechnung in Vue?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn