返回
直击源头,解析 Vue 计算属性的前世今生
前端
2023-09-14 02:10:01
源码中的计算属性
Vue 的计算属性本质上是响应式函数,这可不是我们胡说的,而是 Vue 官方给出的定义:计算属性就是“返回一个被侦听响应式值的函数。”换句话说,只要我们定义了一个计算属性,Vue 就会对这个属性依赖的响应式数据进行侦听。一旦侦听到这些数据发生了变化,Vue 便会自动调用该计算属性对应的函数,重新计算结果并更新 UI。
而在 Vue 的源码中,计算属性被定义为一个特殊的函数,该函数以 getter 和 setter 的形式存在。其中,getter 负责计算属性的值,setter 则负责更新属性的值。
// src/core/instance/computed.js
export 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 (getter === undefined) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
if (!isSSR) {
// create internal watcher for computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// component-defined computed properties are reactive
if (!(key in vm)) {
defineReactive(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
warn(
`The computed property "${key}" is already defined in data.`,
vm
)
}
}
}
缓存机制
既然计算属性是一个函数,那么我们可以想象它每次都会被执行,这样的话,只要它的依赖属性一变,它就会重新计算,然后更新 UI。但问题是,这样的方式开销很大,也会导致性能下降。所以,Vue 在计算属性上做了一些优化,引入了缓存机制。
当计算属性第一次被调用时,Vue 会计算它的值并将其缓存起来。当计算属性的依赖属性发生变化时,Vue 不会立即重新计算该属性,而是先检查缓存。如果缓存中已经存在了该属性的值,Vue 就会直接从缓存中取出并使用。只有当缓存中不存在该属性的值时,Vue 才会重新计算它。
// src/core/instance/computed.js
export function computedWatcherOptions: Object {
get: boolean
}
export function simpleNormalizeWatcher (
userDef: WatcherOptions | Function,
vm: Component
): WatcherOptions {
if (!userDef) {
return {}
}
if (typeof userDef === 'function') {
return {
get: userDef
}
}
return userDef
}
更新机制
当计算属性的依赖属性发生变化时,Vue 会重新计算该属性。计算属性的更新机制分为以下几个步骤:
- Vue 会创建一个新的 watcher,该 watcher 专门负责监听计算属性的依赖属性。
- 当计算属性的依赖属性发生变化时,该 watcher 会被触发,并执行计算属性的 getter 函数。
- 计算属性的 getter 函数会重新计算该属性的值,并将新值返回给 Vue。
- Vue 会将计算属性的新值更新到该属性的响应式对象中。
- Vue 会触发该计算属性的依赖属性的更新,从而更新这些属性对应的视图。
// src/core/instance/computed.js
export function computedWatcherOptions: Object {
lazy: true
}
实例范畴
计算属性的实例范畴指的是该属性所在的组件实例。计算属性只能访问其所在组件实例的数据,而不能访问其他组件实例的数据。
// src/core/instance/computed.js
export function computed (vm: Component, key: string, userDef: Object | Function): any {
const watcher = vm._computedWatchers && vm._computedWatchers[key]
if (watcher) {
if ('value' in watcher) {
return watcher.value
}
const computed = watcher.get()
if (!isRef(computed)) {
watcher.value = computed
}
return computed
}
}
总结
本文对 Vue 源码中计算属性的实现进行了详细的解析,从缓存机制到更新机制,从 getter 和 setter 到实例范畴,希望能帮助大家更好地理解计算属性。