1. 程式人生 > 程式設計 >深入理解Vue的資料響應式

深入理解Vue的資料響應式

1. ES語法的getter和setter

在開始瞭解 vue 的資料響應式原理前應該先搞清楚 ES語法 中的 getter 和 setter 方法的具體用法。

getter和setter 方法是以 get 和 set 關鍵字來為物件新增虛擬屬性的一種方式。這種屬性其實並不真實存在,而是以取值函式 getter 和存值函式 setter 來模擬的一種屬性。目的是對某個屬性設定存值函式和取值函式,攔截該屬性的存取行為,以便於對該屬性的存取做一些限定處理。如下所示(以下程式碼來源於 mdn)

getter 方法

const obj = {
  log: ['a','b','c'],get latest() { //在正常方法前加 get 關鍵字
    if (this.log.length == 0) {
      return undefined;
    }
    return this.log[this.log.length - 1];
  }
}

console.log(obj.latest);// 輸出 c,獲取的是屬性名不用帶括號

setter方法

const language = {
  set current(name) {
    this.log.push(name);
  },log: []
}

language.current = 'EN';
language.current = 'FA';

console.log(language.log);//輸出 Array ["ENHtBNA","FA"]

2. ES語法的 defineProperty

defineProperty 方法會直接在一個物件上定義一個新屬性,或者修改一個物件的現有屬性, 並返回這個物件,可用於在一個物件定義好後為其修改或新增屬性。

語法為:

Object.defineProperty(obj,prop,descriptor)

新增常規屬性:

let data = {
  m: 0
}

Object.defineProperty(data,'n',{
  value: 1 //新增屬性的 value 就是其值
})
console.log(`${data.n}`) //則會輸出n值為1

也可用來新增 getter 和 setter 的虛擬屬性

let data1 = {
    _n: 0
}

Object.defineProperty(data1,{
  get(){
    return this._n
  },set(value){
    if(value < 0) return
    this._n = value
  }//直接寫明 get
程式設計客棧
/ set 即可 }) //由於指明瞭虛擬屬性為 n,即 get n(){}、set n(value){},因此在函式定義時就不用再寫n了

3. Vue對資料的代理和監聽

代理,即 proxy,簡單來說我自己的一些事情我自己不親自處理,而是交給一個人讓他去幫我做,那個做事的人就是代理。這個邏輯中有兩個關鍵點需要搞清楚,代理是處理操作的人,而其處理操作的事情不屬於他,而是屬於委託其代理的人的。

因此類比到 Vue資料代理 ,委託代理的是 data{} 資料物件,其找到代理就是 Vue例項vm ,data{} 資料物件要代理 vm 做的事情是管理 data{} 資料物件裡資料操作。因此 data{} 資料物件只負責內部資料的生產即可,對生產出來的資料的管理和操作全權交給 vm 處理。

那麼 vm 如何對 data{} 資料物件裡的資料進行控制和操作呢?換句話說,vm 如何在 data{} 資料物件裡面的任意一個屬性值變化時都及時知道呢?

於是便用到了 ES 語法中的 getter和setter 方法,通過 getter和setter 方法控制的屬性的任何操作都會被這兩個函式檢測到,而 getter和setter 方法形成的屬性是虛擬屬性,真實並不存在,因此如果使用者想私自不經過代理 vm 直接修改 data{} 資料物件的屬性也獲取不到對應的實體屬性,只能通過 getter和setter 方法修改,那麼其修改就必定被 vm 檢測到。

因此 vm 為了實現對 data{} 資料物件裡資料的全部控制,就必須在 Vue例項 建立的時候對傳進來的 data{} 資料物件做一些處理,做的處理就是將 data{} 資料物件裡的屬性都變成了 getter和setter 方法控制的虛擬屬性,並儲存在代理資料物件 obj 並返回。

但為了不讓使用者直接修改原來的 data{} 屬性,也將原來的 data{} 物件的實體屬性全改變了,新增的虛擬屬性名字和實體屬性名一樣,就會用虛擬屬性覆蓋原來的實際屬性,使用者在修改屬性值是就是通過 getter和setter 方法修改的虛擬屬性。這樣一來 data{} 資料物件的全部屬性的任何變化都會被 Vue例項vm 檢測到。

let myData = {n:0}
let data = proxy({ data:myData }) // 類似於 let vm = new Vue({data: myData})

function proxy({data}/* 解構賦值*/){
  let _n = data.n
  Object.defineProperty(data,{ //覆蓋原來的data.n屬性
    http://www.cppcns.comget(){
      return _n
    },set(newValue){
      if(newValue<0)ret程式設計客棧urn
      _n = newValue
    }
  })// 改變data{}資料物件本身屬性,可通過閉包形成上下文,讓原來的實際屬性值存在閉包的上下文_n中
  
  const obj = {}
  Object.defineProperty(obj,{
    get(){
      return data.n
    },set(value){
      data.n = value
    }
  }) //新增data{}資料物件的代理,對data{}資料物件操作
  
  return obj // obj 就是data{}的代理
}

4. Vue的資料響應式

所謂響應式就是當事物發生變化時會根據變化做出相應的反映。

Vue 中的資料 data 是響應式的,由上述 Vue 通過 Object.defineProperty()函式 來用 getter和setter方法 對 data 資料做了代理和監聽,一旦資料發生變化,Vue 就會改變資料對應的 UI 檢視,這就是 Vue的資料響應式

但是 Vue 使用 Object.defineProperty 來設定監聽,就只能對在 Vue例項化 時 data 物件裡已經存在的屬性設定監聽,而對不存在的或者後來新增進去的屬性沒有進行監聽。

為了解決這個問題,有兩種方法:

1. 將所有屬性都提前宣告好

2. 使用 Vue.set 和 this.$set 新增屬性

使用 Vue.set 和 this.$set 新增屬性是會通知 Vue 對這後新增的屬性也設定監聽操作。

Vue.set('this.data','m','10')
this.$set('this.data','10')//為vm的data物件www.cppcns.com新增屬性m值為10

3.陣列變異

對於陣列的資料增加,無法控制其新增個數因此不能提前宣告所有資料值,而一個一個 set 又太麻煩,而且陣列是常用的物件資料型別中的一種,因此 vue 的作者就對陣列的增刪函式如 push 和 pop 等進行了篡改,使用者在使用 vue 中陣列增刪時仍是用 push 和 pop ,但是裡面進行了額外的處理,這幾個被篡改的 API 會對陣列新增是資料代理監聽並根據資料響應改變 UI 檢視。

以上就是深入理解Vue的資料響應式的詳細內容,更多關於Vue的資料響應式的資料請關注我們其它相關文章!