Vue中的監聽器屬性watch,監聽對象的一些深入研究。

Vue中的偵聽器屬性watch使用的頻率還是非常高的,但是對於其中的一些特性使用起來還是比較模糊,沒有總結的特別到位。有次和同事爲這事爭論了半天,看到對方比較強硬自己就慫了,心想難道是我記錯了?其實還是因爲自己沒有深入的探究過,雖然記得,但卻沒有實際的去驗證!爲了弄清楚這個問題(爲了下次能吵贏 ),今天特意寫了一些demo來驗證一下。

data中有以下屬性,year是普通屬性,yearList是數組,obj是一個對象,其中嵌套了一個內層對象。

  data () {
    return {
      year: 2000,
      yearList: [2000],
      obj: {
        name: 'Wang Ming',
        age: 18,
        favs: ['running', 'swimming', 'dance'],
        brother: {
          name: 'Wang Gang',
          age: 24,
          favs: ['computers', 'eating'],
        }
      }
    }
  },

一、watch監聽普通屬性
watch監聽普通屬性時,可以直接使用我們最常用的寫法:
監聽的屬性名(newValue,oldValue){

}

  watch: {
    year (newValue, oldValue) {
      console.log('時間又過去了1年')
      console.log('新值是' + newValue + ',舊值是' + oldValue)
    },
   }

當改變year時,就會觸發監聽器中的函數。

  methods: {
    grow () {
      this.year++
      this.obj.age++;
      this.yearList.length++
    },
  }

這種方式也是使用的最普遍的方式。只有當綁定的監聽屬性發生改變時,纔會執行綁定的函數。這裏是year改變,就立即執行
在這裏插入圖片描述

二、handler方法和immediate屬性。
但只是這樣,其實還是遠遠不夠的,例如我們如果想要在watch剛綁定的時候,就執行監聽屬性中的函數,應該怎麼辦?
有人說可以在created鉤子函數中再定義一個函數來執行,確實可行,但是會顯得代碼冗餘,不便於維護。以後萬一要修改監聽器屬性中的操作,卻忘了修改created中的函數,就會導致各種無法預料的問題。

其實watch中有現成的方法!
我們修改一下前面的代碼:

  watch: {
    year: {
      handler (newValue, oldValue) {
        console.log('時間又過去了1年,今年是' + newValue)
        console.log('新值是' + newValue + ',舊值是' + oldValue)
      },
      immediate: true,
      deep: false
    },
   }

注意到handler了嗎,我們給 year綁定了一個handler方法,之前我們寫的 year方法其實最終調用的就是這個handler,Vue.js會去處理這個邏輯,最終編譯出來的就是這個handler。
在這裏插入圖片描述
而immediate:true代表如果在 wacth 裏聲明瞭 year之後,就會立即先去執行裏面的handler方法,如果爲 false就跟我們以前的效果一樣,不會在綁定的時候就執行。

三、deep屬性
細心的讀者應該發現了,前面的例子中我還寫了一個deep:false,其實這個屬性默認是爲false的。它代表是否深度監聽。
什麼叫深度監聽?
其實在watch中,監聽普通的屬性變動是沒問題的,但是如果監聽對象屬性或者數組的變動,就會有問題。

  watch: {
     obj: {
      handler (newValue, oldValue) {
        console.log('obj更新了新值是' + newValue + ',舊值是' + oldValue)
      },
    }
   }

這裏不寫deep,則默認deep爲false
下面嘗試改變一下obj.age試試。

  methods: {
    grow () {
      this.year++
      this.obj.age++;
      this.yearList.length++
    },
  }

會發現obj.age改變到21了,但是一直沒有執行obj監聽屬性中綁定的函數。
在這裏插入圖片描述
這種情況就成爲淺監聽!因爲watch無法監聽到對象內層中屬性的變化,只能監聽到整個對象的賦值是否發生變化(後面的例子4會講到)。
但是如果將deep設置爲true

  watch: {
     obj: {
      handler (newValue, oldValue) {
        console.log('obj更新了新值是' + newValue + ',舊值是' + oldValue)
      },
      deep:true
    }
   }

再試試,發現果然輸出了obj監聽屬性中綁定的handler
在這裏插入圖片描述
四、前面提到了淺監聽只能監聽整個對象賦值的變化,這句話怎麼理解呢?
其實如果不想設置deep:true屬性,又想讓對象的監聽器產生作用,那麼就不應該對 對象的屬性直接賦值!而是直接對整個對象重新賦值!

我們先把deep設置爲false

    obj: {
      handler (newValue, oldValue) {
        console.log('obj更新了新值是' + newValue + ',舊值是' + oldValue)
      },
      deep: false
    }
  methods: {
    growup () {
      this.obj = {
        name: 'Wang Ming',
        age: 28,
        favs: ['running', 'swimming', 'dance'],
        brother: {
          name: 'Wang Gang',
          age: 24,
          favs: ['computers', 'eating'],
        }
      }
    },
  }

然後通過growup方法,對對象進行重新賦值,會發現即使deep爲false,依然可以觸發watch!
在這裏插入圖片描述

但是,如果只是爲了修改某個對象的屬性,而讓整個對象重新賦值,這種方法聽起來就挺蠢的!效率低,消耗內存,還容易出錯。萬一少了賦值了一個屬性,導致屬性丟失,前面渲染也會出問題。。。所以這事就當瞭解,除了某些特殊情況下可以用用。

五、watch監聽數組的變化。
再細心一點的讀者朋友們可能會發現,我前面有一個yearList,變成了[2000,null,null],但是卻沒有在console中看到監聽屬性的執行?是我沒有定義對應的watch嗎?
其實我定義了,但是爲了避免讓大家誤解就沒有放出來。

    yearList: {
      handler (newValue, oldValue) {
        console.log('年份列表改變了,新值是' + newValue + ',舊值是' + oldValue)
      },
      deep: true
    },

grow函數來修改數據

    grow () {
      this.year++
      this.obj.age++;
      this.yearList.length++
      this.yearList[0]=1955
    },

在這裏插入圖片描述
大家可以看到,我這裏甚至還設置了deep:true,但是依然沒有監聽到yearList的變動!爲什麼呢?
其實是因爲,我修改yearList的方式是,this.yearList.length++,直接增加了數組的長度,而由於 JavaScript 的限制,Vue 不能檢測以下變動的數組:

當你利用索引直接設置一個項時,例如:vm.items[indexOfItem] = newValue
當你修改數組的長度時,例如:vm.items.length = newLength

官方文檔對此的說明:
https://cn.vuejs.org/v2/guide/list.html#注意事項
但是如果我使用vuejs提供的變異方法,例如push(),就可以實現監聽!

  methods: {
    grow () {
      this.year++
      this.obj.age++;
      this.yearList.push(this.year)
    },
  }

在這裏插入圖片描述
如果你想直接修改數組中的某個值,而不是push數據,官方文檔給出了2種解決方案
一、Vue.set
Vue.set(vm.items, indexOfItem, newValue)
二、Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

你也可以使用 vm.$set 實例方法,該方法是全局方法 Vue.set 的一個別名:
vm.$set(vm.items, indexOfItem, newValue)

爲了解決數組長度變動的問題,你可以使用 splice:
vm.items.splice(newLength)

六、deep:true設置了後,對象中的對象能否監聽到?
既然deep:true設置之後,可以直接監聽對象屬性的變化,那麼對象中的對象能監聽到嗎?
於是我們把obj.age++註釋,增加this.obj.brother.age++,看看王明兄弟的年齡改變,是否會觸發watch

    grow () {
      this.year++
      // this.obj.age++;
      this.yearList.push(this.year)
      this.obj.brother.age++
    },

結果依然是可以的。
在這裏插入圖片描述

七、只監聽對象某個屬性變化的優化。
deep的意思就是深入觀察,監聽器會一層層的往下遍歷,給對象的所有屬性都加上這個監聽器,但是這樣性能開銷就會非常大了,任何修改obj裏面任何一個屬性都會觸發這個監聽器裏的 handler。

但是在實際開發過程中,我們很可能只需要監聽obj中的某幾個屬性,這樣設置deep:true之後就顯得很浪費!
於是我們可以使用字符串形式來優化監聽。
前面obj的監聽可以去掉了!

  watch: {
    'obj.age': {
      handler (newValue, oldValue) {
        console.log('王明的年齡更新了新值是' + newValue + ',舊值是' + oldValue)
      },
      immediate: true
    },
    'obj.brother.age': {
      handler (newValue, oldValue) {
        console.log('王剛的年齡更新了新值是' + newValue + ',舊值是' + oldValue)
      },
      immediate: true
    },
  }

測試發現,完全實現監聽效果~~
在這裏插入圖片描述

結語:在使用watch時,如果需要監聽對象的某具體屬性變動,儘量使用字符串形式來優化監聽!

還有一種方法是通過藉助computed屬性來搭橋,將對象的某個屬性定義爲一個computed屬性,然後返回該對象的屬性值,最後使用watch來監聽該屬性以實現監聽對象屬性的變化。但是我覺得這種方法,過於hack,也增加了代碼的複雜度,就不在這裏提倡了。

終於寫完了,遇到watch相關的問題再不怕了!下次吵架我一定不認慫

參考文章: https://blog.csdn.net/wandoumm/article/details/80259908

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章