Vue之watch监听的原理
前言 vue选项中props、data、watch、methods、computed,其中props、data、computed都通过Object.defineProperty进行数据拦截从而实现响应式。对于选项watch,知道其作用是监听对应key做相关处理,之前一系列文章都没有关注watch背后的实现逻辑,这也是本文的目的。
前言
vue选项中props、data、watch、methods、computed,其中props、data、computed都通过Object.defineProperty进行数据拦截从而实现响应式。对于选项watch,知道其作用是监听对应key做相关处理,之前一系列文章都没有关注watch背后的实现逻辑,这也是本文的目的。
watch实现逻辑
首先通过vue官网的信息说明明确watch的使用方式以及注意点,如下:
watch选项的类型:{ [key: string]: string | Function | Object | Array }
键是需要观察的表达式,值是对应回调函数,可以是string类型函数名、函数、对象、数组(具体可看Vue官网Watch API)。
watch是作为组件的选项,所以在源码中处理逻辑还是initState的处理。
initState处理
initState中watch具体处理逻辑如下:
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
其中有一个注意的逻辑是nativeWatch的比较,这是因为Firefox浏览中Object.prototype存在一个watch函数。
initWatch处理
watch选项的初始化处理逻辑如下:
function initWatch (vm, watch) {
for (var key in watch) {
var handler = watch[key];
if (Array.isArray(handler)) {
for (var i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i]);
}
} else {
createWatcher(vm, key, handler);
}
}
}
遍历watch中每一个key,调用createWatcher函数。其中针对回调函数是数组即多个函数处理程序的情况做处理。
createWatcher函数
// expOrFn参数即watch对应的key
function createWatcher (
vm,
expOrFn,
handler,
options
) {
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
if (typeof handler === 'string') {
handler = vm[handler];
}
return vm.$watch(expOrFn, handler, options)
}
实际上主要逻辑就是调用$wacth实例方法,同时针对回调函数的一些情况的处理:对象、字符串函数名。
$watch处理逻辑
Vue.prototype.$watch = function (
expOrFn,
cb,
options
) {
var vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true;
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
try {
cb.call(vm, watcher.value);
} catch (error) {
handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
}
}
return function unwatchFn () {
watcher.teardown();
}
};
实际上$watch实例方法有3点主要逻辑点:
- Watcher对象生成
- immediate配置:实际上就是立即调用对应的回调函数
- unwatch逻辑处理即取消watch监听
那么watch是如何监听对应key,在key值变化时触发回调逻辑调用?
逻辑在选项watch中watcher对象生成中
涉及选项watch的Watcher构造函数逻辑如下:
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
// 其他代码省略
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
}
this.value = this.lazy
? undefined
: this.get();
};
首先要明确Watcher构造函数的参数含义,主要有:
- vm:对应的vue实例
- expOrFn:用于生成getter表达式,watcher对象的核心执行逻辑
- cb:对应的回调函数
对于watch选项中创建watcher对象,expOrFn实际上就是选项watch中的每一项的key。从Watcher构造函数中针对exporFn生成getter方法,之后会执行getter生成对应的value值。
而针对非函数的expOrFn会调用parsePath函数,实际上就是针对选项watch中生成watcher对象的处理,而这里就是watch原理的关键。
parsePath
var bailRE = /[^\w.$]/;
function parsePath (path) {
if (bailRE.test(path)) {
return
}
// 逗号分割
var segments = path.split('.');
return function (obj) {
for (var i = 0; i < segments.length; i++) {
if (!obj) { return }
// 关键逻辑
obj = obj[segments[i]];
}
return obj
}
}
实际上watch原理就是会获取对应key对应的值,即:
obj = obj[segments[i]];
watch选项中key必须是被数据拦截的属性(没有被拦截是无法监听变化),watch原理就是利用已被拦截属性获取来实现的,基本过程如下:
- parsePath中会生成getter函数之后被触发
- getter函数中会获取key表达式对应的值,而这个获取过程会触发key的getter即会触发依赖收集(实际上就会将watch下key对应的Dep对象与对应生成的watcher对象形成关联)
- 当key对应的表达式形成新的值时,会触发notify从而视图更新,之后会调用Watcher对象的update(实际上同步或异步调用run方法即watcher对象中保存的cb回调函数,对于选项watch下key就是对应的处理函数)
通常watch可以监听data、props这些被拦截的数据很好理解,但实际上watch还可以监听computed:
computed: {
changeMsg() {}
},
watch: {
changeMsg() {}
}
watch原理就是利用已被拦截属性获取来实现的,这个结论是适用的,只不过computed得情况比较特殊,响应式背后的原理核心就是建立dep和watcher之间的关系,computed自身可以作为依赖,但是其不对应一个dep对象,而是computed内部的依赖与其watcher建立关系,而computed作为watch的key也是同样的,是computed内部的dep与watch对应的watcher建立联系。
总结
选项watch监听原理的关键在于:
watch下key必须是被数据拦截的属性
如何实现watch监听?正是因为watch对应key是被数据拦截的属性,在选项watch下:
- 每一个key都会对应一个watcher对象
- watcher对象的getter函数就是获取当前key对应的值,从而建立key对应Dep对象与当前watcher对象建立联系
- 当key改变时就会触发视图更新,从而执行key对应的回调函数
实际上所有监听原理的实现都是Dep对象与Watcher对象建立联系,在Dep对象对应属性改变时触发视图更新,从而运行相关逻辑。
更多推荐
所有评论(0)