让我们先从一些面试题来窥探 computed
吧。
computed
和watch
的区别 ?
computed
是自动监听依赖值的变化,可以监听多个依赖值,从而动态返回内容。
watch
是监听一个值或一个对象,然后当依赖发生变化的时候,可以执行回调函数或者异步操作等。
通常来说仅仅是需要动态值就使用 computed
,如果需要在值改变后执行一些业务逻辑,就使用 watch
。
computed
和method
的区别?
computed
是一个 lazy watch
,只有当依赖发生变化的时候才会重新计算。
method
是当每次触发重新渲染的时候,都会再此执行函数。
Vue computed 实现
- 在 new Vue() 的时候,会先执行
initState
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
- 可以在上面的函数里看到会调用
initComputed
方法,那我们看一下这个方法的实现
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
for (const key in computed) {
// 拿到 getter
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
// 为 computed 的属性创建内部watcher,注意传入的 computedWatcherOptions
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
defineComputed(vm, key, userDef)
}
}
这里我们可以看到,我们先声明一个空对象,再拿到 computed
的 getter
,如果是 function
就取该函数,否则就取其 get
。接着我们创建一个 Watcher
实例,注意第四个参数是 { lazy: true }
-
然后我们看这个
Watcher
类,在core/observer/watcher
中定义,它接收5个参数,因为我们这个不是渲染watcher
,所有第五个参数我们不需要传入
可以看到我们在initComputed
中实例化Watcher
的时候,传入了一个computedWatcherOptions
,该参数作为options
,可以看到,this.lazy
和this.dirty
都为true
。
然后最关键的是this.value = this.lazy ? undefined : this.get()
。这里和平常的watcher
是不同的,这里如果this.lazy = true
,那么其是不会去求值的。 那什么之后执行我们定义在computed
中的方法呢?因为这里的逻辑已经执行完毕,initComputed
中只剩一个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)
: userDef
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop
sharedPropertyDefinition.set = userDef.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
方法的第一步是创建一个完整的 sharedPropertyDefinition
,因为我们只是给 computed
设置一个方法,并非自定义 getter
, 并且是非服务端渲染,所以进入 sharedPropertyDefinition.get = createComputedGetter(key)
和 sharedPropertyDefinition.set = noop
, 最后用新的 sharedPropertyDefinition
把 computed
的这个 key 挂在到 vm上,当你访问这个属性的时候,就会执行 sharedPropertyDefinition
中的 get 方法。这个方法其实就是 createComputedGetter(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
}
}
}
createComputedGetter
返回了一个 computedGetter
函数,也就是说我们访问计算属性这个 key 的时候,会执行 computedGetter
函数,因为我们在 initComputed
的时候执行过
const watchers = vm._computedWatchers = Object.create(null)
所以我们这里可以拿到 watcher,因为我们在实例化的时候传入的 lazy = true
,所以这里会执行 watcher.evaluate
方法
evaluate () {
this.value = this.get()
this.dirty = false
}
这里才会执行 this.get()
,进行求值。在我们执行this.get
的时候,会为 Dep.target 赋值,所以就会执行 watcher.depend()
,这里会对计算属性进行依赖收集,订阅该计算属性中的所有依赖值,当他们发生变化的时候就会 notify
该计算属性重新计算。
当所依赖的值改变的时候,会通知计算属性,执行 update
方法,这时才会把 this.dirty = true
,如果依赖值没有改变,那么多次访问该计算属性,不会重新计算,返回的是上一次的计算值。
所以, computed
是一个 lazy watch
。