官方解釋:
觀察 Vue 實(shí)例變化的一個(gè)表達(dá)式或計(jì)算屬性函數(shù)涛舍⊥醮眨回調(diào)函數(shù)得到的參數(shù)為新值和舊值浴栽。表達(dá)式只接受監(jiān)督的鍵路徑。對于更復(fù)雜的表達(dá)式涎劈,用一個(gè)函數(shù)取代
1. 偵聽器的基本使用
偵聽器可以監(jiān)聽data對象屬性或者計(jì)算屬性的變化
watch是觀察屬性的變化
所以watch的屬性名必須要與觀察人的名字保持一致;
只要觀察的值發(fā)生了變化才會(huì)觸發(fā),
<div id="app">
<!-- 監(jiān)聽器 -->
<input type="text" v-model="msg">
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
msg:""
},
watch:{
msg(){
console.log("數(shù)據(jù)發(fā)生了變化")
console.log(arguments)
}
}
})
</script>
通過這個(gè)理解,我們就會(huì)發(fā)現(xiàn), 只要數(shù)據(jù)一但發(fā)生變化,那么監(jiān)聽函數(shù)msg就會(huì)被觸發(fā), 監(jiān)聽函數(shù)中接受兩個(gè)參數(shù),第一個(gè)參數(shù)是數(shù)據(jù)變化后的新值, 第二個(gè)參數(shù)是數(shù)據(jù)變化后的舊值
盡管大部分時(shí)間我們用不到偵聽器, 但偵聽器對于處理異步操作非常適合,
例如我們需要將用戶輸入的內(nèi)容延遲5秒后現(xiàn)在在頁面上
<div id="app">
<!-- 監(jiān)聽器 -->
<input type="text" v-model="msg">
{{showMsg}}
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
msg:"",
showMsg: ""
},
watch:{
msg(){
let newValue = this.msg
setTimeout(() => {
this.showMsg = newValue
},5000)
}
}
})
</script>
2. 獲取舊值
偵聽器在數(shù)據(jù)發(fā)生變化的時(shí)候就會(huì)觸發(fā),觸發(fā)時(shí),數(shù)據(jù)已經(jīng)更新,我們那到就是新值,那么我們?nèi)绾潍@取之前的舊值呢
其實(shí)當(dāng)監(jiān)聽的屬性發(fā)生變化時(shí),偵聽器會(huì)被傳入兩個(gè)參數(shù)
第一個(gè)參數(shù):偵聽器所監(jiān)聽屬性的當(dāng)前值,即更新后的值
第二個(gè)參數(shù): 原來舊值
<div id="app">
<!-- 監(jiān)聽器 -->
<input type="text" v-model="msg">
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
msg:"",
},
watch:{
msg(val, oldval){
console.log(val);
console.log(oldval);
}
}
})
</script>
3. 監(jiān)聽data對象中某個(gè)對象的屬性
data屬性中的數(shù)據(jù)值除了是基本數(shù)據(jù)類型的數(shù)據(jù)為,還有可能是對象類型,那么我們?nèi)绾伪O(jiān)聽對象數(shù)據(jù)的屬性的
<div id="app">
<!-- 監(jiān)聽器 -->
<input type="text" v-model.number="fruit.price">
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
fruit:{
name:"蘋果",
price: 20
},
},
watch:{
fruit(val, oldval){
console.log(val);
console.log(oldval);
}
}
})
</script>
如果我們按照之前的監(jiān)聽方式, 那么我們就會(huì)發(fā)現(xiàn),當(dāng)我們修改fruit屬性值的時(shí)候,偵聽器不會(huì)被觸發(fā), 偵聽器會(huì)在fruit對象整體被修改時(shí)觸發(fā).
為了監(jiān)聽對象里某個(gè)特定屬性的變化,可以在偵聽器的名稱中使用.操作符, 就像訪問這個(gè)對象的屬性
<div id="app">
<!-- 監(jiān)聽器 -->
<input type="text" v-model.number="fruit.price">
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
fruit:{
name:"蘋果",
price: 20
},
},
watch:{
"fruit.price"(val, oldval){
console.log(val);
console.log(oldval);
}
}
})
</script>
4. 深度監(jiān)聽
通過上面的例子,我們知道,我們可以監(jiān)聽對象的特定屬性的變化,,可以我們想監(jiān)聽整個(gè)對象的所有屬性的變化就需要給對象所有的屬性添加監(jiān)聽就不是特別的好,如果我們只是單純的監(jiān)聽對象,那么屬性的變化并不會(huì)觸發(fā)監(jiān)聽器,只有整個(gè)對象被替換式才會(huì)觸發(fā)
所以我們可以通過deep屬性來開啟對象的深度監(jiān)聽,
4.1 deep 選項(xiàng)
為了發(fā)現(xiàn)對象內(nèi)部值的變化,可以在選項(xiàng)參數(shù)中指定 deep: true
阅茶。注意監(jiān)聽數(shù)組的變更不需要這么做蛛枚。
如果需要開啟深度監(jiān)聽,那么監(jiān)聽器將不再是一個(gè)函數(shù),而需要寫成一個(gè)對象,對象中配置deep屬性
<div id="app">
<!-- 監(jiān)聽器 -->
<input type="text" v-model.number="fruit.price">
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
fruit:{
name:"蘋果",
price: 20
},
},
watch:{
fruit:{// 此時(shí)fruite 就是一個(gè)配置對象,里面的屬性都是配置選項(xiàng)
// handler 就是原來的監(jiān)聽函數(shù), 當(dāng)數(shù)據(jù)變化是執(zhí)行的函數(shù)
handler(val,oldval){
console.log(val);
console.log(oldval);
},
// 深度監(jiān)聽選項(xiàng)
deep:true
}
}
})
</script>
此時(shí)我們就做到了即監(jiān)聽這整個(gè)對象的變化, 也簡體對象里面所有的屬性的變化,
4.2 immediate選項(xiàng)
監(jiān)聽除了deep選項(xiàng)外,還有immediate選項(xiàng)
指定 immediate: true
將立即以表達(dá)式的當(dāng)前值觸發(fā)回調(diào),
watch:{
fruit:{// 此時(shí)fruite 就是一個(gè)配置對象,里面的屬性都是配置選項(xiàng)
// handler 就是原來的監(jiān)聽函數(shù), 當(dāng)數(shù)據(jù)變化是執(zhí)行的函數(shù)
handler(val,oldval){
console.log(val);
console.log(oldval);
},
// 深度監(jiān)聽選項(xiàng)
deep:true,
immediate: true // 理解執(zhí)行監(jiān)聽函數(shù)handler
}
}
5. 引用類型深度監(jiān)聽后,屬性變化,獲取新舊值問題
但是細(xì)心的朋友就會(huì)發(fā)現(xiàn)我們在改變對象屬性的時(shí)候,雖然觸發(fā)了偵聽器,但是我沒發(fā)獲取舊值了,我們拿到的兩個(gè)形參的值都是對象更改后的新值.
出于某種原因沒有深入過濾對象的每個(gè)屬性,那么只能監(jiān)聽到對象的變化脸哀,而JavaScript里對象的賦值是引用賦值蹦浦,雖然屬性變化了,但是它引用的地址卻一直沒有變化撞蜂,這樣的話盲镶,當(dāng)對象的屬性值改變了侥袜,Vue雖然知道它改變了,但也只能循著引用地址去獲得對象溉贿,可此時(shí)對象的屬性的值已經(jīng)改變了枫吧,因此Vue并不能得到變異之前的值。
示例:
<div id="app">
<!-- 監(jiān)聽器 -->
<input type="text" v-model.number="fruit.price">
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
fruit:{
name:"蘋果",
price: 20
},
},
watch:{
fruit:{
handler(val,oldval){
console.log(11)
console.log(val);
console.log(oldval);
},
deep:true
}
},
})
</script>
此時(shí)當(dāng)數(shù)據(jù)發(fā)生變化是, 查看handler 兩個(gè)參數(shù)值
5.1 解決方案: 利用計(jì)算屬性
官方方案: 觀察 Vue 實(shí)例變化的一個(gè)表達(dá)式或計(jì)算屬性函數(shù)宇色【旁樱回調(diào)函數(shù)得到的參數(shù)為新值和舊值。表達(dá)式只接受監(jiān)督的鍵路徑宣蠕。對于更復(fù)雜的表達(dá)式例隆,用一個(gè)函數(shù)取代
既然 watch
無法在變異對象或數(shù)組時(shí)監(jiān)聽新舊值,那么我們可以先使用JSON.parse()
來淺復(fù)制一遍data對象抢蚀,然后在復(fù)制的對象上修改镀层,完了重新賦值給該data對象,這樣變化前后兩個(gè)對象是完全不一樣的,因?yàn)樗鼈兊囊玫刂吠耆灰粯用笄琕ue可以循著兩個(gè)引用地址獲得新舊兩個(gè)
<div id="app">
<!-- 監(jiān)聽器 -->
<input type="text" v-model.number="fruit.price">
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
fruit:{
name:"蘋果",
price: 20
},
},
watch:{
fruitNew:{
handler(val,oldval){
console.log(11)
console.log(val);
console.log(oldval);
},
deep:true
}
},
computed:{
fruitNew(){
return JSON.parse(JSON.stringify(this.fruit));
}
},
})
</script>
6. 可以通過Vue是實(shí)例對象的$watch屬性來監(jiān)聽
除了 watch
選項(xiàng)之外唱逢,您還可以使用命令式的 vm.$watch ,通過Vue實(shí)例對象來監(jiān)聽數(shù)據(jù)
6.1 普通監(jiān)聽
可以通過實(shí)例對象調(diào)用$watch設(shè)置監(jiān)聽
<!-- 監(jiān)聽字符變化-->
<div id="app">
<!-- 監(jiān)聽器 -->
<input type="text" v-model="msg">
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
msg:'你好'
}
})
// $watch 是一個(gè)實(shí)例方法
vm.$watch("msg",(val,newVal) => {
console.log(val)
console.log(newVal);
})
</script>
6.2 監(jiān)聽配置
<div id="app">
<!-- 監(jiān)聽器 -->
<input type="text" v-model.number="fruit.price">
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
fruit:{
name:"蘋果",
price: 20
},
}
})
// $watch 是一個(gè)實(shí)例方法
vm.$watch("fruit",(val,newVal) => {
console.log(val)
console.log(newVal);
},{
deep: true
})
</script>