侦听器的概念
watch侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作- 适用场景: 当需要在数据变化时执行异步或开销较大的操作
- 本质: 对象( 键是需要侦听的数据,值是对应回调函数/方法名/对象/数组 )
- 使用: 定义到
watch节点下 - 内置参数: 新值、旧值
值为函数
<input type="text" v-model="username">
data(){
return { username: 'zhangsan' }
},
watch: {
username(newVal, oldVal) {
console.log('旧值:', oldVal);
console.log('新值:', newVal);
}
}
值为方法名
watch: {
username:'resize'
},
methods: {
resize(newVal,oldVal){
console.log(newVal,oldVal);
}
}
值为对象
watch: {
username:{
handler:'resize',
immediate:true // 立即触发选项
}
},
值为数组
watch: {
username:[
//第一个回调
'resize',
// 第二个回调
function(){
console.log('第二个回调');
},
// 第三个回调
{
handler(){
console.log('第三个回调');
}
}
]
},
methods: {
resize(newVal,oldVal){
console.log('第一个回调');
}
}
键为数据名
<input type="text" v-model="user.name">
data(){
return {
user:{ name: 'zhangsan' }
}
},
watch: {
'user.name':function(newVal,oldVal){
console.log('新值',newVal);
console.log('旧值',oldVal);
}
},
异步操作
- 使用
watch选项允许执行异步操作(访问一个API),限制执行该操作的频率,并在我们得到最终结果前,设置中间状态
<h1>
Ask a yes/no question:
<input v-model="question">
</h1>
<h1>{{ answer }}</h1>
import axios from 'axios'
import _ from 'loadsh'
export default {
data(){
return {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
}
},
created() {
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
watch: {
// question发生改变,函数执行
question(newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
// 添加防抖,AJAX 请求直到用户输入完毕才会发出
this.debouncedGetAnswer()
}
},
methods: {
getAnswer() {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
// 数据请求
axios.get('https://yesno.wtf/api')
.then((res)=> {
this.answer = _.capitalize(res.data.answer)
})
.catch(function (error) {
this.answer = 'Error! Could not reach the API. ' + error
})
}
}
};
侦听器选项
-
一般侦听器有两个缺点:
- 无法在刚进页面的时自动触发
- 侦听对象时,对象的属性发生变化,不能触发侦听器
immediate选项
watch默认是懒侦听,最初绑定时不会执行,需等到侦听的属性改变时才执行回调- 在
watch中首次绑定时,让侦听器自动触发一次
watch: {
username:{
immediate:true,
handler(newVal,oldVal){
console.log('新值',newVal);
console.log('旧值',oldVal);
}
}
},
deep选项
watch默认是浅层,嵌套属性的变化不会被侦听到- 若想侦听所有嵌套属性的变更,需要深层侦听器
watch: {
user:{
deep:true,
handler(newVal,oldVal){
console.log('新值',newVal);
console.log('旧值',oldVal);
}
}
}
注意:由于侦听的是对象,相当于是侦听该对象的地址,所以获取到的新旧值都是同一个地址,这就会导致新旧值一致的问题
侦听器的工作原理
- 在
new Vue()时调用initState方法,其中调用了initWatch方法
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
- 实例上存在
watch配置并且watch不是原生方法时才执行,因为火狐浏览器是自带watch属性的
initWatch函数
- 作用: 遍历
watch对象,然后拿到其每一项作为handler - handler: 可以是一个数组,若是一个数组就遍历并调用
createWatcher,否则直接调用
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);
}
}
}
createWatcher函数
- 作用: 创建侦听器
function createWatcher (vm, expOrFn, handler, options) {
// vm:组件实例
// expOrFn:侦听的数据
// handler:侦听数据对应回调函数/方法名/对象/数组
// isPlainObject用于判断是否为对象
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
if (typeof handler === 'string') {
handler = vm[handler];
}
return vm.$watch(expOrFn, handler, options)
}
- 如果对应的回调是对象,则将对象中的
handler函数赋给形参handler - 如果对应回调是字符串,则直接调用实例上的方法
- 返回调用
$watch
$watch函数
Vue.prototype.$watch = function (expOrFn, cb, options) {
var vm = this;
// 如果对应的回调是对象,则调用createWatcher
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) {
var info = "callback for immediate watcher "" + (watcher.expression) + """;
pushTarget();
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info);
popTarget();
}
return function unwatchFn () {
watcher.teardown();
}
};
-
首先判断对应的回调若是对象,则调用
createWatcher方法,因为watch方法是可以直接调用的,它可以传递一个对象,也可以传递函数 -
然后让将传入的配置项赋给
options,再把属性user置为true,这是user watcher标识 -
new一个Watcher实例,其中四个参数分别为①vm: 组件实例
②expOrFn: 监听的数据
③cb: 回调函数
handler④options: 配置项
在
Watcher构造函数中,如果expOrFn是合法字符串,就返回一个函数赋到watch的getter中
- 返回函数的话
this.getter就是一个取值函数,最后会调用this.get()进行一次求值,对响应式对象取值就触发get()拦截,这时就会收集依赖user watcher - 通过实例化
Watcher,一旦侦听器的数据发生变化,最终会执行Watcher的run方法,执行回调
- 如设置了
immediate选项,同样执行invokeWithErrorHandling方法,唯一不同的是调用回调之前执行了pushTarget(),回调之后又调用popTarget(),这种做法其实是防止收集依赖 - 最后返回
unwatchFn方法,目的主要是移除侦听器
总结
- 通过分析,大概了解到侦听器是如何工作的。
- 侦听器本质上是一个
user watch - 侦听器主要用于观测数据变化去完成开销较大的复杂业务逻辑
- 若想深入了解,还需要挖掘源码