Vue中的Computed实现原理分析
作者:秦JaccLink
在 Vue.js 中,computed 属性是一种强大的特性,用于定义依赖于其他响应式数据的计算值。
computed 属性不仅能够简化模板中的表达式,还能够缓存计算结果,避免不必要的重复计算,从而提高性能。
将深入探讨 Vue 中 computed 属性的实现原理,包括其工作机制、依赖追踪、缓存策略等方面。
1. Computed 属性概述
Computed 属性是 Vue 实例中的一个特殊属性,它允许开发者定义一个计算值,该值依赖于其他响应式数据。
Computed 属性具有以下特点:
- 响应式:当依赖的数据发生变化时,computed 属性会自动重新计算。
- 缓存:computed 属性会缓存计算结果,只有当依赖的数据发生变化时,才会重新计算。
- 惰性求值:computed 属性在首次访问时才会进行计算,之后会根据依赖数据的变化情况决定是否重新计算。
2. Computed 属性的基本用法
在 Vue 实例中,可以通过 computed
选项来定义 computed 属性。
new Vue({ data() { return { firstName: 'John', lastName: 'Doe' }; }, computed: { fullName() { return `${this.firstName} ${this.lastName}`; } } });
在上述代码中,fullName
是一个 computed 属性,它依赖于 firstName
和 lastName
。
当 firstName
或 lastName
发生变化时,fullName
会自动重新计算。
3. Computed 属性的实现原理
3.1 依赖追踪
Vue 的 computed 属性实现依赖于 Vue 的响应式系统。
Vue 通过 Object.defineProperty
或 Proxy
来劫持数据的变化,并在数据变化时通知依赖该数据的观察者。
3.1.1 响应式数据劫持
Vue 在初始化数据时,会通过 Object.defineProperty
或 Proxy
对数据进行劫持,使其变为响应式数据。
function defineReactive(obj, key, val) { const dep = new Dep(); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { if (Dep.target) { dep.depend(); } return val; }, set(newVal) { if (newVal === val) return; val = newVal; dep.notify(); } }); }
在上述代码中,defineReactive
函数通过 Object.defineProperty
劫持了对象的属性,并在 get
和 set
方法中分别收集和通知依赖。
3.1.2 依赖收集
在 computed 属性被访问时,Vue 会通过 Dep.target
来收集依赖。
functionWatcher(vm, expOrFn, cb) { this.vm = vm; this.getter = parsePath(expOrFn); this.cb = cb; this.value = this.get(); } Watcher.prototype.get = function() { Dep.target = this; const value = this.getter.call(this.vm, this.vm); Dep.target = null; return value; }; Watcher.prototype.update = function() { const oldValue = this.value; this.value = this.get(); this.cb.call(this.vm, this.value, oldValue); };
在上述代码中,Watcher
实例在 get
方法中将自身设置为 Dep.target
,然后访问 computed 属性,从而触发依赖数据的 get
方法,完成依赖收集。
3.2 缓存策略
Computed 属性具有缓存机制,只有在依赖数据发生变化时,才会重新计算。
3.2.1 缓存实现
Vue 通过 Watcher
实例的 dirty
属性来控制缓存。
function Watcher(vm, expOrFn, cb, options) { this.vm = vm; this.getter = expOrFn; this.cb = cb; this.dirty = this.lazy = !!options.lazy; this.value = this.lazy ? undefined : this.get(); } Watcher.prototype.evaluate = function() { this.value = this.get(); this.dirty = false; }; Watcher.prototype.get = function() { pushTarget(this); let value; const vm = this.vm; try { value = this.getter.call(vm, vm); } finally { popTarget(); } return value; }; Watcher.prototype.update = function() { if (this.lazy) { this.dirty = true; } else { this.run(); } };
在上述代码中,Watcher
实例的 dirty
属性用于标记 computed 属性是否需要重新计算。
当依赖数据发生变化时,Watcher
的 update
方法会将 dirty
设置为 true
,表示需要重新计算。
3.2.2 惰性求值
Computed 属性在首次访问时才会进行计算,之后会根据 dirty
属性决定是否重新计算。
function createComputedGetter(key) { return function computedGetter() { const watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { if (watcher.dirty) { watcher.evaluate(); } if (Dep.target) { watcher.depend(); } return watcher.value; } }; }
在上述代码中,createComputedGetter
函数返回一个 computed 属性的 getter 函数。
在访问 computed 属性时,如果 dirty
为 true
,则会调用 watcher.evaluate
方法进行计算,并将 dirty
设置为 false
,表示计算结果已缓存。
4. Computed 属性的优化
4.1 避免不必要的计算
在定义 computed 属性时,应尽量避免不必要的计算。
例如,如果 computed 属性的计算逻辑较为复杂,可以考虑将其拆分为多个简单的 computed 属性。
computed: { fullName() { return `${this.firstName} ${this.lastName}`; }, formattedName() { return this.fullName.toUpperCase(); } }
4.2 使用 Watcher 进行性能优化
在某些情况下,可以使用 watch
选项来替代 computed 属性,以实现更细粒度的控制和性能优化。
watch: { firstName: 'updateFullName', lastName: 'updateFullName' }, methods: { updateFullName() { this.fullName = `${this.firstName} ${this.lastName}`; } }
5. 总结
Vue 的 computed 属性通过依赖追踪和缓存策略,实现了响应式计算和性能优化。
在实现原理上,computed 属性依赖于 Vue 的响应式系统,通过 Watcher
实例进行依赖收集和缓存控制。
通过深入理解和掌握 computed 属性的实现原理,开发者可以更好地利用这一特性,提高应用的性能和可维护性。
在实际开发中,应根据具体需求合理使用 computed 属性,并结合其他优化手段,如避免不必要的计算和使用 Watcher 进行细粒度控制,从而构建高效、稳定的 Vue 应用。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。