定义:
computed选项用于定义计算属性,计算属性是基于其他数据的值动态计算的,并且是在其依赖的数据发生变化时动态更新。 watch选项用于观察和响应Vue实例上数据的变化。它提供一个更灵活的方法处理数据变化,当你需要在数据发生变化时,执行异步操作或负责逻辑时。
共同点:
- 每个定义的computed和watch属性都会生成一个watcher。都是基于watcher实现的。
不同点
- watch支持异步,computed不支持异步
- computed是基于其依赖缓存的,只有在依赖发生变化时才会重新计算。如果没有发生变化,直接返回之前的缓存值,不会重新计算。watch不会缓存值,依赖每次发生变化都会重新执行回调函数
原理:
computed: 实现化watcher,实例化时传入lazy:true。watcher.dirty从lazy取值为true。当调用到计算属性时,第一次就会执行watcher.evaluate(),这个函数执行watcher.get去计算computed的get方法,将计算的值缓存到watcher.value并将watcher.dirty改为false,代表这是最新的值。如果依赖的响应式数据没有发生变化时,dirty就为false,不会重新计算,直接返回watcher.value的值。
const computedWatcherOptions = { lazy: true }
function initComputed(vm: Component, computed: Object) {
const watchers = (vm._computedWatchers = Object.create(null))
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
export function defineComputed(
target: any,
key: string,
userDef: Record<string, any> | (() => any)
) {
const shouldCache = !isServerRendering()
if (isFunction(userDef)) {
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 (__DEV__ && 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) {
if (__DEV__ && Dep.target.onTrack) {
Dep.target.onTrack({
effect: Dep.target,
target: this,
type: TrackOpTypes.GET,
key
})
}
watcher.depend()
}
return watcher.value
}
}
}
watch: 实例化watcher, exporFn参数为监听的属性,handler参数响应式数据发生变化时要执行的函数
function initWatch(vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
function createWatcher(
vm: Component,
expOrFn: string | (() => any),
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
Vue.prototype.$watch = function (
expOrFn: string | (() => any),
cb: any,
options?: Record<string, any>
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn() {
watcher.teardown()
}
}