在Vue.js中,计算属性是一个非常实用的特性,它允许你根据其他数据动态计算出新的值,并且会缓存计算结果,只有当依赖的数据发生变化时才会重新计算。下面我们来深入解读Vue计算属性的源码实现。
Vue的计算属性实现主要涉及到以下几个关键步骤:
Watcher
实例,用于监听依赖数据的变化。Watcher
的缓存机制来存储计算属性的计算结果。Watcher
进行更新。在Vue实例的初始化过程中,会调用initComputed
函数来初始化计算属性。该函数位于src/core/instance/state.js
文件中。
function initComputed (vm: Component, computed: Object) {
// 用于存储计算属性的watcher
const watchers = vm._computedWatchers = Object.create(null)
// 计算属性是否为服务器渲染
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
// 获取计算属性的getter函数
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) {
// 为每个计算属性创建一个watcher
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
{
lazy: true // 标记为懒执行,即只有在访问计算属性时才会计算
}
)
}
// 为计算属性定义getter和setter
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)
}
}
}
}
在initComputed
函数中,为每个计算属性创建了一个Watcher
实例,并且将lazy
选项设置为true
,表示该Watcher
是懒执行的,只有在访问计算属性时才会计算。
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
{
lazy: true
}
)
Watcher
类在src/core/observer/watcher.js
文件中定义,其中evaluate
方法用于计算计算属性的值,并将结果缓存起来。
class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
// ...
this.lazy = !!options.lazy
this.dirty = this.lazy // 标记是否需要重新计算
// ...
}
// 计算计算属性的值
evaluate () {
this.value = this.get()
this.dirty = false // 标记为不需要重新计算
}
// 获取计算属性的值
get () {
pushTarget(this) // 开始依赖收集
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
popTarget() // 结束依赖收集
this.cleanupDeps()
}
return value
}
}
当访问计算属性时,会触发计算属性的getter
函数,在getter
函数中会调用Watcher
的evaluate
方法来计算计算属性的值,并进行依赖收集。当依赖数据发生变化时,会通知计算属性的Watcher
进行更新,将dirty
标记设置为true
,表示需要重新计算。
function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.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)
}
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
}
}
}
通过以上源码解读,我们可以了解到Vue计算属性的实现原理:
Watcher
实例,并将其标记为懒执行。getter
函数,调用Watcher
的evaluate
方法计算计算属性的值,并进行依赖收集。Watcher
的dirty
标记会被设置为true
,表示需要重新计算。这样,Vue就实现了计算属性的缓存和自动更新功能。