computed和watch的区别
标签:computed,watch,vm,Watcher,handler,key,源码,属性 From: https://www.cnblogs.com/dgqp/p/17351013.html
computed
和watch
的相同点。底层都会创建一个watcher
(用法的区别:computed
定义的属性可以在模板中使用,watch
不能在视图中使用)
computed
默认不会执行 只有取值的时候才会执行 内部会维护一个 dirty 属性,来控制依赖的值是否发生变化。默认计算属性需要同步返回结果(有个包,让computed
变成异步的 0)。
watch
默认用户会提供一个回调函数,数据变化了就调用这个回调。我们可以监控某个数据的变化 数据变化了执行某些操作。
computed
:
- 通过Object.defineProperty实现计算属性的属性劫持;
- 在组件实例中创建Watcher实例,用于监听计算属性的依赖项变化;
- 在Getter函数中进行依赖收集,即将当前Watcher实例添加到Dep实例中;
- 在Setter函数中不进行任何操作,因为计算属性不支持双向绑定;
- 通过缓存机制避免重复计算和浪费资源;
- 在依赖项发生变化时触发Watcher实例的更新,并通知相关组件进行视图更新。
export function initState (vm: Component) { // 省略部分代码 const computed = options.computed if (computed) { for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get if (!getter || typeof getter !== 'function') { // 省略部分代码 } // 创建Watcher实例,并将其添加到computedWatchers对象中,并设置该计算属性对应的Getter函数 defineComputed(vm, key, getter, userDef) } } // 省略部分代码 } // 定义defineComputed函数,用于创建计算属性的Getter函数及Watcher实例 export function defineComputed ( target: any, key: string, userDef: Object | Function, setter?: Function ) { if (typeof userDef === 'function') { sharedPropertyDefinition.get = createComputedGetter(key) // 创建计算属性的Getter函数 sharedPropertyDefinition.set = setter // 设置计算属性的Setter函数(不支持双向绑定) } else { sharedPropertyDefinition.get = userDef.get ? userDef.cache !== false ? createComputedGetter(key) // 创建计算属性的Getter函数 : userDef.get : noop sharedPropertyDefinition.set = userDef.set || noop } Object.defineProperty(target, key, sharedPropertyDefinition) }
总结一下:
- 通过
defineComputed
函数定义计算属性,并创建计算属性对应的Watcher
实例;- 在
createComputedGetter
函数中创建计算属性的Getter
函数,在其中调用计算属性对应Watcher
实例的evaluate
方法进行计算,并在needSpend
时间过长等情况下设置dirty标记,表示计算结果已经失效,需要重新计算;- 在计算属性的
Getter
函数中进行依赖收集,并将计算属性的Watcher
实例添加到Dep.target
对应的Watcher
实例的依赖项中;- 在计算属性的
Setter
函数中不进行任何操作,因为计算属性不支持双向绑定;- 通过缓存机制避免重复计算和浪费资源;
- 在依赖项发生变化时触发计算属性的
Watcher
实例的更新,并通知相关组件进行视图更新。
watch
:
- 通过
initWatch
函数遍历watch
对象,为每个属性创建Watcher
实例,并将其添加到异步更新队列中进行更新;- 通过
$watch
方法手动创建观察者,内部调用createWatcher
函数创建Watcher
实例,并设置Watcher
实例所依赖的数据项和回调函数等信息;- 在
Watcher
类中承担了依赖收集和更新的核心功能;- 在计算属性和
watch
中都需要为每个依赖项创建Watcher
实例,但两者实现方式略有不同。// 省略部分代码 export function initState (vm: Component) { // ... const { watch, computed } = options if (watch) { for (const key in watch) { const handler = watch[key] if (Array.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 | Function, 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) } // 省略部分代码
实现
$watch
方法:export function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } } } export function stateMixin (Vue: Class<Component>) { // ... Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { const vm: Component = this if (isPlainObject(cb)) { // 如果cb是一个对象,则options为传入的第三个参数,cb取该对象的handler属性 return createWatcher(vm, expOrFn, cb, options) } options = options || {} options.user = true // 将user标记设置为true,表示当前Watcher实例由用户手动创建 const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) { // 如果设置了immediate选项,则立即执行回调函数 cb.call(vm, watcher.value) } return function unwatchFn () { watcher.teardown() } } }