- vue实例组件初始化过程中,在执行initState(vm)方法初始化状态时,判断options.watch有值时会进行initWatch(vm, options.watch)处理,然后对watch对象中的每个watch属性执行createWatcher方法
javascript
function initState(vm) {
// 传入的watch
if (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方法
javascript
function createWatcher(vm, expOrFn, handler, options) {
// 对象
if (handler.toString() === '[object Object]') {
options = handler
handler = handler.handler
}
// 监听的方法是methods里的方法
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
- 执行$watch方法,给属性创建一个对应的watcher观察者实例,用来依赖收集。如果设置了immediate:true标识,表示立即执行一次回调函数
javascript
Vue.prototype.$watch = function (expOrFn, cb, options) {
options = options || {}
// 用户自己设置的watch
options.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数组中
javascript
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;
// 触发响应式数据的getter
while (i--) _traverse(val[keys[i]], seen);
}
}
- 当监听的响应式数据发生变化时,触发watcher.update()方法,最终执行watcher.run()方法,执行回调函数。
总结:watch与computed的区别
- watch无缓存功能,所监听的响应式属性值发生变化时,都会立即触发回调函数;
- watch更灵活,可以监听任何响应式数据或表达式的变更;
- watch支持深度监听,可支持在回调函数中执行异步操作,如网络请求等等。适用场景:数据变更后执行自定义逻辑,如网络请求、DOM操作。需要监听深层对象结构变化时。
- computed依赖其他数据属性进行计算,并返回计算结果,必须return出去一个值;
- computed具有缓存功能,计算结果进行缓存,提高性能。适用场景:所需的值是要根据多个响应式属性计算得出,并且会被频繁使用的值;