文章目录

案例引入

我们实现以下的一个小案例:

Vue核心⑤(监视属性)_vue.js


点击切换天气会分别显示凉爽、炎热(只有这两种状态),且每一次天气切换的时候都会被控制台给捕获到。

我们先不考虑控制台的那一部分,先把切换部分给做出来。

我们可以使用前文刚刚介绍的计算属性。第一部分实现代码如下:

<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>

<script type="text/javascript">.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
})</script>

此处有两个注意点:
①如果我们把上述代码模板中的插值语法给去掉,也就是这样:

Vue核心⑤(监视属性)_javascript_02


因为此时已有属性、计算属性在模板当中都没有使用,故会导致Vue的开发者工具出现一点小问题。


我们发现,点击了切换按钮之后,开发者面板中的属性值并没有发生变化:


Vue核心⑤(监视属性)_回调函数_03


事实是:数据已经更新了,但是开发者面板中没有更新,我们可以验证一下:


Vue核心⑤(监视属性)_vue.js_04


②一个小技巧


​@click​

​后面可以写一些简单的语句(就是你回调函数里面的语句),例如上面的代码我们还可以这样写:


Vue核心⑤(监视属性)_vue.js_05


同样也可以进行多项操作(理论上回调函数里面所有的语句都可以写在里面):


Vue核心⑤(监视属性)_vue.js_06


效果:


Vue核心⑤(监视属性)_vue.js_07


不过这么写容易乱套,建议逻辑简单的时候可以使用这种技巧,如果复杂还是中规中矩的写回调函数。


同时这个方法还有一个注意点:不是所有的语句都可以执行的。例如:


Vue核心⑤(监视属性)_前端_08


报错:


Vue核心⑤(监视属性)_javascript_09


因为alert是window身上的方法,而模板之中把对象给写死了就是vm,不会说找不到再去外头一层找(但会再去vm的原型对象上继续找,当然肯定是找不到的)。故这里alert是不能用的。


当然这种情况你非要解决也是有办法的(可以但没必要),就是让window可见就行:


Vue核心⑤(监视属性)_回调函数_10


我们直接将window放在data中,这样在模板中就可见了。

然后控制台的捕获部分就要使用监视属性了。

监视属性

监视属性顾名思义就是我们要监视的属性。那么我们如何去监视一个属性呢。Vue为我们提供了一个新的配置项​​watch​​。我们直接看相关实现代码,再进行解释:

<body>
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>

<script type="text/javascript">.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
watch:{
isHot:{
immediate:true, //初始化时让handler调用一下
//handler什么时候调用?当isHot发生改变时。
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
}
}
})</script>

watch里面的被监视属性同样也要写成一个配置对象。其中:

  • handler是一个回调函数,当被监视的属性值发生变化的时候就会被调用。它会被传入两个参数:一个新值,一个旧值。
  • immediate是一个布尔属性,默认值是false。当设置为true的时候表示初始化的时候也要调用以下handler

不论是已有的属性还是计算属性都可以被监视

我们也可以不通过watch来进行监视,我们可以直接通过vm实现监视(前提是确保vm实例对象已经被创建完毕),代码如下:

<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>

<script type="text/javascript">.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
/* watch:{
isHot:{
immediate:true, //初始化时让handler调用一下
//handler什么时候调用?当isHot发生改变时。
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
}
} */
})

vm.$watch('isHot',{
immediate:true, //初始化时让handler调用一下
//handler什么时候调用?当isHot发生改变时。
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
})</script>

注意:$watch()这个方法的第一个参数是要监视的对象。这个地方是有引号的。我们对象中的属性不写引号是一种简写形式,如果写全的话也是有引号的(因为是字符串类型)。

那么这两种我们如何选择呢?
如果一开始,也就是创建vm实例对象的时候,就已经很明确要监视谁,就可以直接选择watch配置,
如果创建实例的时候不是很明确要检测谁,后续根据用户的行为才能确定。那么就可以选择vm的相应api来监视

小总结:

Vue核心⑤(监视属性)_前端_11

深度监视

我们还是通过一个案例来理解:我们想检测number中的a的变化(b如果发生变化不管)

Vue核心⑤(监视属性)_前端_12


网页端:

Vue核心⑤(监视属性)_vue.js_13


也就是说监视多级结构中某个属性的变化,代码实现如下:

Vue核心⑤(监视属性)_回调函数_14


之所以加引号是因为对象里的key必须是字符串,里面直接写​​.​​是不可以的,所以我们只能还原原始的写法。实现效果:

Vue核心⑤(监视属性)_javascript_15

现在我们有新的需求,我们想要检测numbers中所有属性的变化(也就是说只要其中一种属性发生了变化就会被监测到),如果numbers中有很多很多的属性,继续沿用上面的代码的思路显然代码会非常冗长麻烦。这个时候我们就可以使用​​深度监视​​!

我们只需要添加一个配置项​​deep​​即可。它的默认值是false,以此来保证程序的运行效率。

监视多级结构中所有属性的变化,代码如下:

<script type="text/javascript">.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
isHot:true,
numbers:{
a:1,
b:1,
}
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
watch:{
isHot:{
// immediate:true, //初始化时让handler调用一下
//handler什么时候调用?当isHot发生改变时。
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
},
//监视多级结构中某个属性的变化
/* 'numbers.a':{
handler(){
console.log('a被改变了')
}
} */
//监视多级结构中所有属性的变化
numbers:{
deep:true,
handler(){
console.log('numbers改变了')
}
}
}
})</script>

小结:
深度监视:
(1).Vue中的watch默认不监测对象内部值的改变(一层)。
(2).配置deep:true可以监测对象内部值改变(多层)。
备注:
(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
(2).使用watch时根据数据的具体结构,决定是否采用深度监视。

监视的简写形式

使用前提:当你监视属性的配置项中只有handler的时候才可以使用。

与前文的计算属性的简写方法一致,直接写成一个函数。函数名对应监视的函数名,方法体就是handler的方法体。

代码如下:

<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>

<script type="text/javascript">.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
watch:{
//正常写法
/* isHot:{
// immediate:true, //初始化时让handler调用一下
// deep:true,//深度监视
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
}, */
//简写
/* isHot(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue,this)
} */
}
})</script>

同样的我们也可以使用vm的api进行监视,常规及简写版如下:

//正常写法
vm.$watch('isHot',{
immediate:true, //初始化时让handler调用一下
deep:true,//深度监视
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
})

//简写
vm.$watch('isHot',(newValue,oldValue)=>{
console.log('isHot被修改了',newValue,oldValue,this)
})

注意:此处不能写箭头函数(Vue管理的函数不能写成箭头函数)

watch对比computed

我们发现有时候一个功能可以用这两种方式分别实现。例如我们还是使用前面的一个小案例:有3个input框,姓、名、姓名。当姓和名发生变化的时候,姓名会跟着动态的发生变化。

watch实现:

<body>
<div id="root">
姓:<input type="text" v-model:value="firstName"> <br>
名:<input type="text" v-model:value="lastName"> <br>
姓名:<input type="text" :value="fullName">
</div>

</body>
<script>const vm = new Vue({
el:"#root",
data:{
firstName:'张',
lastName:'三',
fullName:'张-三'
},
watch:{
firstName(newValue){
this.fullName = newValue + '-' + this.lastName
},
lastName(newValue){
this.fullName = this.firstName + '-' + newValue
},
}

})</script>

computed实现:

Vue核心⑤(监视属性)_javascript_16


从这里可以看出使用计算属性的代码是比监听属性更加简洁的。但是这并不是绝对的,有时候我们要实现的功能甚至只有监听属性才能做。

下面我们讲将案例进行修改:我们要求修改之后的变化延迟一秒才生效。

代码实现如下:
使用监视属性实现:

<script>const vm = new Vue({
el:"#root",
data:{
firstName:'张',
lastName:'三',
fullName:'张-三'
},
watch:{
firstName(newValue){
setTimeout(() => {
this.fullName = newValue + '-' + this.lastName
}, 1000);
},
lastName(newValue){
setTimeout(() => {
this.fullName = this.firstName + '-' + newValue
}, 1000);
},
}

})</script>

使用计算属性是不能完成这个需求的。因为计算属性的变化全靠return,而我们是没有办法实现延迟return的。

如果你用setTimeout函数将return包裹,那么return里的东西返回给了延时函数,而不是计算属性的那个函数,相当于外层函数没有返回值,那么相应的计算属性也不会发生变化

Vue核心⑤(监视属性)_javascript_17

所以我们可以知道:
计算属性里面是不能开启异步任务去维护数据的。但是使用监视属性是可以完成的。

当有一个需求,watch能实现computed也能实现,我们一般使用计算属性。但是当我们面临一些异步的计算的时候,我们就要使用watch.

同时还有一个小问题:

Vue核心⑤(监视属性)_javascript_17


我们在这个箭头函数里面使用了this,那么它指向的是Vue的实例对象吗?

确实如此。我们可以在此基础上进行总结:

  1. 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
  2. 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。