Maison >interface Web >js tutoriel >Quel est le principe de mise en œuvre du calcul dans Vue ?
Bien que la pile technologique actuelle ait été transférée de Vue vers React, l'expérience réelle de développement de plusieurs projets à l'aide de Vue est toujours très agréable. La documentation de Vue est claire et standardisée, la conception de l'API est simple et efficace et elle est conviviale. aux développeurs front-end. La mise en route est rapide, et je pense même personnellement qu'utiliser Vue dans de nombreux scénarios est plus efficace que le développement de React. J'ai déjà étudié le code source de Vue par intermittence, mais je ne l'ai jamais résumé, donc je l'ai fait. Je vais faire ici un résumé technique et approfondir ma compréhension de Vue. Ce que je vais donc écrire aujourd'hui, c'est le principe de mise en œuvre de computed
, l'une des API les plus couramment utilisées dans Vue.
Pas grand chose à dire, un exemple de base est le suivant :
<div> <p>{{fullName}}</p> </div>
new Vue({ data: { firstName: 'Xiao', lastName: 'Ming' }, computed: { fullName: function () { return this.firstName + ' ' + this.lastName } } })
Dans Vue nous n'avons pas besoin de calculer directement dans le template{{this.firstName + ' ' + this.lastName}}
, car mettre trop de logique déclarative dans le modèle rendra le modèle lui-même en surpoids, surtout lorsqu'un grand nombre d'expressions logiques complexes sont utilisées pour traiter les données dans la page, cela aura un grand impact sur la maintenabilité du page. Et computed
a été initialement conçu pour résoudre de tels problèmes.
watch
Bien sûr, souvent lorsque nous utilisons computed
, nous le comparons souvent avec une autre API dans Vue, qui est l'écouteur watch
, car dans un certain Ils sont cohérents à certains égards et sont basés sur le mécanisme de suivi des dépendances de Vue. Lorsqu'une certaine donnée de dépendance change, toutes les données ou fonctions associées qui dépendent de ces données changeront automatiquement ou seront appelées.
watch
. Cette approche est particulièrement utile lorsque vous devez effectuer des opérations asynchrones ou coûteuses lorsque les données changent. D'après l'explication de watch
dans la documentation officielle de Vue, nous pouvons comprendre que l'utilisation de l'option watch
nous permet d'effectuer des opérations asynchrones (accès à une API) ou des opérations hautes performances, limiter la fréquence à laquelle nous effectuons le opération, et avant d'obtenir le résultat final, nous définissons des états intermédiaires, ce que les propriétés calculées ne peuvent pas faire.
Ce qui suit résume également quelques différences supplémentaires entre computed
et watch
:
computed
consiste à calculer un nouvel attribut, et montez l'attribut sur la machine virtuelle (instance Vue), et watch
consiste à surveiller les données qui existent déjà et qui ont été montées sur vm
, donc l'utilisation de watch
peut également surveiller l'attribut calculé de computed
Change ( d'autres incluent data
, props
)
computed
est essentiellement un observateur évalué paresseusement avec possibilité de cache Seulement lorsque la dépendance change, la première La nouvelle valeur ne sera pas calculée. jusqu'à ce que l'attribut computed
soit accédé plusieurs fois, et watch
appellera la fonction d'exécution
lorsque les données changent, computed
Une donnée est appliquée à. être affecté par plusieurs données, et watch
est appliqué à une donnée pour affecter plusieurs données
Ci-dessus, nous comprenons certaines des différences entre computed
et watch
La différence entre les scénarios d'utilisation, bien sûr, parfois les deux ne sont pas aussi clairs et stricts. En fin de compte, il est quand même nécessaire d'analyser l'activité spécifique.
Revenons au sujet de l'articlecomputed
Afin de mieux comprendre le mécanisme interne des propriétés calculées, explorons-le étape par étape. le code source de Vue. Le principe d'implémentation.
Avant d'analyser le computed
code source, nous devons d'abord avoir une compréhension de base du système réactif de Vue. Vue l'appelle un système réactif non intrusif. Le modèle de données n'est qu'un objet JavaScript ordinaire. eux, la vue est mise à jour automatiquement.
Lorsque vous transmettez un objet JavaScript normal à l'option
data
d'une instance de Vue, Vue parcourra toutes les propriétés de cet objet et utilisera Object.defineProperty
Convertissez toutes ces propriétés en getter/setter
. Ces getter/setter
sont invisibles pour l'utilisateur, mais en interne, elles permettent à Vue de suivre les dépendances et de notifier les changements lorsque les propriétés sont accédées et modifiées. Chaque instance de composant a un objet d'instance watcher
correspondant. enregistrera les propriétés en tant que dépendances lors du rendu des composants. Plus tard, lorsque le setter
de la dépendance sera appelé, il demandera à watcher
de recalculer, provoquant la mise à jour de ses composants associés. Le système de réponse Vue comporte trois points essentiels : 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()
计算求值。
Lorsque le composant est initialisé, computed
et data
créeront le leur Le système de réponse, Observer
parcourt chaque paramètre d'attribut dans data
get/set
interception de données
initialisation computed
appellera la initComputed
fonction
Enregistrez une instance watcher
et instanciez un abonné au message Dep
à l'intérieur pour les dépendances de collection ultérieures (telles que watcher
de la fonction de rendu ou autre watcher
qui observe les changements dans la propriété calculée) )
déclenchera la fonction d'accès Object.defineProperty
de son get
pour appeler sa propre méthode watcher.depend()
Ajouter un autre attribue à dep
subs
watcher
de watcher
(qui à son tour appelle la méthode evaluate
de watcher
) Devenez abonné des autres abonnés au message get
. Attribuez d'abord watcher
à watcher
, puis exécutez la fonction d'évaluation Dep.target
lors de l'accès aux propriétés dans la fonction d'évaluation (comme depuis getter
, ou autre data
), leur fonction accesseur props
sera également déclenchée pour ajouter le computed
de la propriété calculée à l'abonné au message get
du watcher
de la propriété dans la fonction d'évaluation, lorsque ces opérations seront terminées. , fermez enfin watcher
et attribuez-le à dep
et renvoyez le résultat de la fonction d'évaluation. Dep.target
null
de son propre abonné au message set
pour parcourir le Enregistrez le tableau dep
de tous les abonnés notify
et appelez la méthode dep
une par une pour terminer la mise à jour de la réponse. wathcer
subs
watcher
update
Recommandations associées :
contrôleur thinkphp Principe de implémentation de l'étape display()
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!