第一次尝试写技术文章,结合之前看过的一些讲解computed的文章,以及结合案例我将分享我自己的理解,如有错误还请指出,虚心请教。
实例:computed里的b值依赖于data里面a的值
<template>
<div>
<button @click="getB">获取b的值</button>
</div>
</template>
<script>
export default {
data(){
return{
a:1
}
},
computed:{
b(){
return this.a+4
},
c:{
get(){
return this.a+2
}
}
},
mounted(){
console.log(this.b)
this.initGetB()
},
methods:{
initGetB(){
console.log(this.b)
},
getB(){
this.a=9
console.log(this.b)
}
}
}
</script>
先看一下计算属性的初始化发生在Vue实例初始阶段的initState函数中,根据if(opts.computed)执行了initComputed(vm, opts.computed)
看一下initComputed函数
const computedWatcherOptions = { lazy: true }
function initComputed(vm: Component, computed: Object) {
// 首先创建了watcher,vm._computedWatchers为一个空对象
const watchers = vm._computedWatchers = Object.create(null)
// 判断是否是服务端渲染
const isSSR = isServerRendering()
//遍历computed对象
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
)
}
if (!(key in vm)) {
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
//这里也解释了为什么computed里面有的属性data里面无需定义
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)
}
}
}
}
- 函数首先创建了
vm._computedWatchers一个空对象 - 对computed对象遍历
const userDef = computed[key]相对案例即:const userDef=conputed['b']=function(){return this.a+4}。判断userDef是函数还是对象,因为computed还有一种以对象的形式通过set,get来设值即案例中c值的写法,无论怎样都是获取对应的getter方法。 - 为computed对应的每一个key创建一个computed watcher
- 在确定计算属性与已有data、props属性不重名的情况下,调用defineComputed
继续看一下defineComputed的实现
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export 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)
}
defineComputed核心就是调用Object.defineProperty(target,key,sharedPropertyDefinition)将computed这个key挂载到vm上,当访问这个属性的时候(this.b实际上就是访问vm上的b属性而不是computed里面的b属性)就会调用getter,在上面我们定义了sharedPropertyDefinition这个对象针对对象的key监听做的处理
再看一下createComputedGetter这个函数
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
}
}
}
其中watcher.evaluate根据dirty来决定是computed独有的函数。
这个方法返回了computedGetter函数,他就是计算属性对应的getter
最后看一下watcher类
// /src/core/observer/watcher.js
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.dirty = this.lazy // for lazy watchers
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this)
let value
const vm = this.vm
//执行了计算属性定义的getter函数
value = this.getter.call(vm, vm)
popTarget()
this.cleanupDeps()
//通过return value拿到计算属性对应的值
return value
}
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
evaluate () {
if (this.dirty) {
this.value = this.get()
this.dirty = false
}
return this.value
}
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
}
首先this.dirty=this.lazy这个根据传入的lazy初始化的时候设置为true
下面就根据实例的调用来分析一下过程
1.在mounted里面调用console.log(this.b)时,会针对b这个属性执行对应的get函数,也就是之前通过sharedPropertyDefinition.get = createComputedGetter(key)(我们不考虑是不是服务器渲染这种情况)调用createComputedGetter函数,createComputedGetter函数根据dirty初始化为true时,执行watcher里面的evaluate函数。evaluate函数又根据this.dirty=true去调用this.get()函数,执行value=this.getter.call(vm,vm),实际上就是执行了计算属性定义的getter函数,返回value值。
2.在mounted里接着执行this.initGetB()函数时,由于之前console.log(this.b)执行时将dirty设置了false,所以就不再执行evaluate调用get函数了,而是直接将上一次通过this.get()函数获取并设置在this上的value值返回。这就是computed缓存的点。
3.当点击按钮button执行getB的时候,改变了a的值,而b依赖a,分析一下这个依赖的过程。
当我们修改a值的时候,就会触发a属性对应的setter,在a属性对应的setter里面会通知所有订阅它变化的watcher,也就是去执行watcher.update函数,在update函数里根据lazy重新将dirty设置为了true,方便下次再调用的时候重新调用get函数而不是直接获取之前的value值
以上就是个人见解,最关键的就是根据dirty值来控制是否来取缓存还是重新计算。