- vue实例组件初始化过程中,在执行initState(vm)方法初始化状态时,判断options.watch有值时会进行initWatch(vm, options.watch)处理,然后对watch对象中的每个watch属性执行createWatcher方法
function initState(vm) {// 传入的watchif (options.watch) { initWatch(vm, options.watch)}
}
function initWatch(vm, watch) {for (let key in watch) {let handler = watch[key]// 这里不写数组的情况了,平时没用过createWatcher(vm, key, handler)}
}
- 执行createWatcher方法,拿到watch属性的具体回调函数,再执行vm.$watch方法
function createWatcher(vm, expOrFn, handler, options) {// 对象if (handler.toString() === '[object Object]') {options = handlerhandler = handler.handler}// 监听的方法是methods里的方法if (typeof handler === 'string') {handler = vm[handler]}return vm.$watch(expOrFn, handler, options)
}
- 执行$watch方法,给属性创建一个对应的watcher观察者实例,用来依赖收集。如果设置了immediate:true标识,表示立即执行一次回调函数
Vue.prototype.$watch = function (expOrFn, cb, options) {options = options || {}// 用户自己设置的watchoptions.user = true let watcher = new Watcher(this, expOrFn, cb, options)// 立即执行if (options.immediate) {pushTarget();cb.apply(this, [watcher.value])popTarget();}
}
- 创建watcher实例,进行依赖收集。如果watch的属性是定义在data中的,此watcher实例会被push进data中对应的属性的Dep对象的subs数组中。如果watch的属性是计算属性,此watcher实例会被push进计算属性中所有响应式数据的Dep对象的subs数组中
class Watcher {constructor(vm, expOrFn, cb, options) {//...if (options) {this.user = !!options.user;this.deep = !!options.deep; // 是否深度监听this.lazy = !!options.lazy;}this.vm = vm;this.cb = cb; // 回调函数this.active = true;// 获取watch属性在vm实例上对应的值this.getter = parsePath(expOrFn); this.value = this.lazy ? undefined : this.get();}get() {pushTarget(this);let vm = this.vm;let value = this.getter.call(vm, vm);// 深度监听if (this.deep) {traverse(value);}popTarget();return value;}update() {// 进入异步队列,这个之前讲过,会触发this.run()queueWatcher(this);}run() {let value = this.get();if (value !== this.value || isObject(value) || this.deep) {let oldValue = this.value;this.value = value;if (this.user) {// 执行回调函数this.cb.apply(this.vm, [value, oldValue]);}}}
}// 获取watch属性在vm实例上对应的值
// 可以是data中定义的属性,也可以是计算属性
function parsePath(path) {let segments = path.split(".");return function (obj) {if (!obj) return;for (let i = 0; i < segments.length; i++) {obj = obj[segments[i]];}return obj;};
}
function isObject(obj) {return obj !== null && typeof obj === "object";
}// 递归处理触发每一个属性的getter,形成深度依赖收集
let seenObjects = new Set();
function traverse(val) {_traverse(val, seenObjects);seenObjects.clear(); // 清空return val;
}
function _traverse(val, seen) {let i;let keys;if (val.__ob__) {var depId = val.__ob__.dep.id;// 处理循环引用的情况if (seen.has(depId)) {return;}seen.add(depId);}if (Array.isArray(val)) {i = val.length;while (i--) _traverse(val[i], seen);} else {keys = Object.keys(val);i = keys.length;// 触发响应式数据的getterwhile (i--) _traverse(val[keys[i]], seen);}
}
- 当监听的响应式数据发生变化时,触发watcher.update()方法,最终执行watcher.run()方法,执行回调函数。
总结:watch与computed的区别
- watch无缓存功能,所监听的响应式属性值发生变化时,都会立即触发回调函数;
- watch更灵活,可以监听任何响应式数据或表达式的变更;
- watch支持深度监听,可支持在回调函数中执行异步操作,如网络请求等等。适用场景:数据变更后执行自定义逻辑,如网络请求、DOM操作。需要监听深层对象结构变化时。
- computed依赖其他数据属性进行计算,并返回计算结果,必须return出去一个值;
- computed具有缓存功能,计算结果进行缓存,提高性能。适用场景:所需的值是要根据多个响应式属性计算得出,并且会被频繁使用的值;