Heim >Web-Frontend >js-Tutorial >Was ist das Implementierungsprinzip der Berechnung in Vue?
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.
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.
watch
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.
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:
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
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
Eins Daten werden von mehreren Daten beeinflusst, und computed
gilt für einzelne Daten, die sich auf mehrere Daten auswirken. watch
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
: Um den internen Mechanismus berechneter Eigenschaften besser zu verstehen, lassen Sie uns ihn Schritt für Schritt untersuchen Vue-Quellcode Das Implementierungsprinzip. computed
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
Wenn Sie ein normales JavaScript-Objekt an die Option
einer Vue-Instanz übergeben, durchläuft Vue alle Eigenschaften dieses Objekts und verwendetdata
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. watcher
Das Vue-Antwortsystem hat drei Kernpunkte: , observe
, watcher
: dep
observe
:遍历 data
中的属性,使用 Object.defineProperty 的 get/set
方法对其进行数据劫持;
dep
:每个属性拥有自己的消息订阅器 dep
,用于存放所有订阅了该属性的观察者对象;
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
函数(其前后也分别初始化了 initData
和 initWatch
)并传入两个参数 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) } } } }
从这段代码开始我们观察这几部分:
获取计算属性的定义 userDef
和 getter
求值函数
const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get
定义一个计算属性有两种写法,一种是直接跟一个函数,另一种是添加 set
和 get
方法的对象形式,所以这里首先获取计算属性的定义 userDef
,再根据 userDef
的类型获取相应的 getter
求值函数。
计算属性的观察者 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() } } }
为了简洁突出重点,这里我手动去掉了我们暂时不需要关心的代码片段。
观察 Watcher
的 constructor
,结合刚才讲到的 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>
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
前面的代码可以看到 sharedPropertyDefinition
的 get/set
方法在经过 userDef
和 shouldCache
等多重判断后被重写,当非服务端渲染时,sharedPropertyDefinition
的 get
函数也就是 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()
计算求值。
Wenn die Komponente initialisiert wird, erstellen computed
und data
ihre eigenen Das Antwortsystem Observer
durchläuft jede Attributeinstellung in data
get/set
Datenabfang
Initialisierung computed
ruft die initComputed
Funktion
-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
-Zugriffsfunktion Object.defineProperty
get
aufzurufen. Andere hinzufügen Attribute zu watcher.depend()
dep
subs
watcher
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. dep
Dep.target
null
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. wathcer
subs
watcher
update
Verwandte Empfehlungen:
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!