Vue计算属性原理
前言
计算属性是Vue中比较好用的API,开发者可以利用计算属将复杂的计算进行缓存,同时基于它的响应式特性,我们无需关注数据更新问题,它会在依赖的数据发生变化时做出相应的变化。但需要注意的是,计算属性是惰性求值的。本文将详细介绍计算属性的实现原理。
初始化
组件初始化时会调用挂载在Vue原型链上的_init
方法,即Vue.prototype._init
:
function Vue(options) {
this._init(options)
}
同时_init
方法会调用initState
方法,这个方法主要用来初始化props、methods、data、watch以及本文所介绍的computed:
Vue.prototype._init = function (options?: Record<string, any>) {
// ...省略一些代码
initState(vm)
// ...省略一些代码
}
为了看上去一目了然,这里省略了一些代码(包括判断条件)
export function initState(vm: Component) {
// ..省略了一些代码
initProps(vm, opts.props)
initMethods(vm, opts.methods)
initData(vm)
initComputed(vm, opts.computed)
initWatch(vm, opts.watch)
}
走到这里之后,程序就进入了初始化计算属性的过程中
initComputed
在这个方法里,主要做了两件事:
- 遍历用户传入的计算属性,生成相应的
Wacher
,每个Watcher
都被标记为lazy
- 在组件实例
vm
上挂载同名属性,值为一个按条件生成的getter
函数
以下代码有省略
function initComputed(vm: Component, computed: Object) {
const watchers = (vm._computedWatchers = Object.create(null))
for (const key in computed) {
const userDef = computed[key];
// 获取用户自定义的计算属性getter
const getter = isFunction(userDef) ? userDef : userDef.get;
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
{ lazy: true }
);
defineComputed(vm, key, userDef);
}
}
其中new Watcher
会生成Watcher
实例:
export default class Watcher implements DepTarget {
constructor(
vm: Component | null,
expOrFn: string | (() => any),
cb: Function,
options?: WatcherOptions | null,
isRenderWatcher?: boolean
) {
this.getter = expOrFn
this.value = this.lazy ? undefined : this.get();
}
因为我们传入的lazy
配置,Wacher不会执行Watcher.prototype.get()
方法,这个方法主要用于依赖收集,感兴趣的同学可以去看一下:Watcher.prototype.get
defineComputed
这个方法主要是在组件实例上生成相应的计算属性,便于我们在组件内部通过this[key]
的方式进行获取,主要代码如下:
export function defineComputed(
target: any,
key: string,
userDef: Record<string, any> | (() => any)
) {
sharedPropertyDefinition.get = createComputedGetter(key);
Object.defineProperty(target, key, sharedPropertyDefinition)
}
这里的target
就是组件实例,key
即为我们定义的相应的响应式属性名,它的值是一个
生成的getter
;
createComputedGetter
有了访问属性的方式,那么就需要返回相应的计算属性值,这个方法主要用来实现计算属性的惰性求值的:
function createComputedGetter(key) {
return function computedGetter() {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
// ...
return watcher.value
}
}
}
这里返回的函数就是用于设置Object.defineProperty
的getter的存取描述符get
,它会根据watcher.dirty
,判断是否需要调用方法watcher.evaluate()
进行求值。
在watcher.evaluate
方法里主要做两件事:
- 调用
get
方法进行依赖收集(会调用我们在计算属性上定义的函数,从而触发相应的响应式数据的getter进行依赖收集) - 将
watcher
的dirty
标志位置为false
this.value = this.get()
this.dirty = false
完成了上述步骤后,整个响应式属性就建立完毕,当计算属性依赖的数据发生变化时,会调用watcher.update()
方法,这个方法会再次将dirty
标志位写为false
。当我们在模版里或其他地方访问组件实例上的响应式属性后,就会触发上述定义的createComputedGetter
返回的函数。从而进行wacher.evaluate()
求值进行重新计算,计算完成后又将dirty
置为false
,等待下一次重新计算。如果我们不妨问这个属性,那么是不会触发getter,从而进行计算的(惰性求值)。
总结
方法调用流程:
组件初始化 -> _init()
-> initState
-> initComputed
-> definedComputed
-> createComputedGetter
数据流:
watcher.evaluate
-> 依赖收集 -> dirty = false
-> update
-> dirty = true
-> 用户访问计算属性 -> watcher.evaluate
-> 循环此过程...
纸上得来终觉浅 绝知此事要躬行
转载自:https://juejin.cn/post/7236286358388473914