深入 Vue 2.x 數據響應式原理 ★

目錄

1、Vue 對 data 做了什麼

2、ES6 的 getter 和 setter

2、Object.defineProperty

3、proxy 代理數據的安全性

4、Vue 的數據響應式原理


1、Vue 對 data 做了什麼

Vue 的數據響應式是它的一大特點,也是它的一大優勢,Vue 中的 data 數據模型僅僅是普通的 JavaScript 對象,而當你修改它們時,視圖會進行更新,這使得狀態管理非常簡單直接,那 Vue 到底對 data 做了什麼呢? Vue 官方解釋:傳送門

const myData = {    //將實例中的data抽出來
  n: 0
}
console.log(myData);    //頁面會打印{n:10}

new Vue({
  data: myData,    //將myData傳遞給Vue實例的data屬性
  template: ` <div>{{n}}</div> `
}).$mount("#app");

setTimeout(()=>{
  myData.n += 10;
  console.log(myData);  //頁面會打印{n:(...)}
},3000)

上述實例中,第一次打印的 myData 數據是 { n:0 } 的形式,第二次打印的 myData 數據是 { n:(...) } 的形式,這中間經歷了將 myData 傳遞給 Vue 實例的 data 屬性的過程,這個過程肯定是 Vue 對 myData 做了什麼導致它的形式變化,往下看!

2、ES6 的 getter 和 setter

了在解 Vue 數據響應式原理以及上述實例中的打印 myData 發生變化的原因之前,我們先來了解一下 ES6 的 getter 和 setter,對象屬性可以是一個 函數gettersetter 方法。get 和 set 也是對象的屬性,只不過它們是以函數的形式定義的。

let person = {                                     let obj = {  //對象屬性可以是一個 函數、getter、setter 方法
  姓: "程",                                           property: function ([parameters]) {},
  名: "序員",                                         get property() {},
  get 姓名() {                                        set property(value) {},
    return this.姓 + this.名;                      };
  },
  set 姓名(xxx){
    this.姓 = xxx[0];
    this.名 = xxx.slice(1);
  }
};
console.log(person.姓名);  //打印:程序員    //getter 相當於不加 () 的函數
person.姓名 = '舒化奶';     //用 = xxx 觸發 set 函數
console.log(person);       //打印:{age:18,姓:舒,名:化奶,姓名:(...)}    和Vue中的myData一樣的效果

上述實例在打印 person 對象時,會發現 person 對象中多了一個 姓名 屬性,它的形式和 Vue 中的 myData 的形式類似。我們將 person 對象的 姓名:(...) 屬性展開,裏面包含了 get 姓名:f 姓名() set 姓名:f 姓名(xxx) 

這表示,用戶確實可以對 person 的姓名屬性進行讀寫,但實際上 person 對象並不存在該姓名屬性。由此類比 Vue 中的 myData 對象,打印後的 { n:(...) } 表示 Vue 實例上的 data 對象中的 n,並不會原始屬性,而用戶卻可以通過 get/set 操作它。

那 Vue 爲什麼要把 data 變成這種 get/set 的形式呢,這和 Vue 的數據響應式原理有什麼關係?往下看!

2、Object.defineProperty

Object.defineProperty() 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性,並返回此對象,附上該屬性的 mdn 文檔,感興趣的同學可以移步瞭解一下 → 傳送門

let person = {
  _age: 18
};

//如果一個對象被聲明完了之後需要對其新增 get/set... 屬性的話,需使用Object.defindProperty
Object.defindProperty(person,'age',{
  value: 88,    //給 person 對象添加一個值爲 88 的 age 屬性!
  get(){ return this._age },
  set(value){   //這裏可對定義的屬性進行篩選操作,比如添加 if 語句,可以保證設置的值滿足條件才生效
    if(value < 0) return;
    this._age = value 
  }
})

上述這種方式可以實現篩選功能,比如在 set() 方法中添加 if 判斷語句,從而實現有條件的設置對象的屬性值,如果設置的值是正數就進行賦值,如果是負數就不進行設置。但是要注意,getter/setter 是在對象聲明的時候直接用的, 如果想在聲明後繼續給對象添加 getter/setter 屬性,則需要通過 Object.defineProperty() 的方法。

3、proxy 代理數據的安全性

上述實例可以實現數據的篩選設置功能,但是並不能保證 person._age 屬性的安全性,因爲其他開發人員可以通過 persong._age = -1;的方式直接將其設置爲一個負值,爲了解決這種安全性問題,我們可以使用下面這種 proxy 代理的方法!

let data1 = proxy({ data:{n:0} }); //括號裏是匿名對象,無法訪問

function proxy({data}){  //{data}是結構賦值的寫法
  const obj = {}
  Object.defineProperty(obj, 'n', { //這裏的'n'寫死了,理論上應該遍歷data的所有key
    get(){
      return data.n;
    },
    set(value){
      if(value < 0) return;
      data.n = value
    }
  })
  return obj; // obj 就是代理,這樣可以保證 data 中的數據不會被直接通過 data.n 的方式進行篡改
};    //但是這種方式並不是銀彈!
console.log(data1.n);    //可以拿到 data1.n 屬性,但是不能通過 data1.n=-1 進行篡改

let myData = {n:0};    //但是如果將data設置成一個引用值
let data = proxy({ data:myData }); //括號裏是匿名對象,無法訪問
myData.n = -1;    //依然可以對屬性 n 進行直接篡改! 往下看!
let myData = {n:0}
let data2 = proxy({ data:myData }) // 括號裏是匿名對象,無法直接訪問,但是可以訪問引用 myData

function proxy({data}){  //{data}是結構賦值的寫法
  let value = data.n;    //獲取data中的myData的n屬性
  Object.defineProperty(data, 'n', {    //創建一個n的虛擬屬性,進行監聽,防止被偷改:delete data.n
    get(){
      return value;
    },
    set(newValue){
      if(newValue < 0) return
      value = newValue;
    }
  })    // 就加了上面幾句,這幾句話會監聽 data

  const obj = {}
  Object.defineProperty(obj, 'n', {
    get(){
      return data.n;
    },
    set(value){
      if(value < 0) return    //這句話多餘了
      data.n = value;
    }
  })
  
  return obj; // obj 就是代理
}

上述實例是隻向外暴露代理對象,開發人員無法直接通過 data.n = -1;的方式設置 data 的屬性。示例鏈接:傳送門

4、Vue 的數據響應式原理

上述講到的實例其實和 Vue 的數據響應式原理類似,如下代碼:

let data2 = proxy({ data: myData }) 
let vm = new Vue({ data: myData })

實際上 Vue 在聲明實例的時候:const vm = new Vue({data:myData});做了如下兩件事情:

  • 一、會讓 vm 成爲 myData 的代理(proxy)。
  • 二、會對 myData 的所有屬性進行監控,實際上是代理的內部虛擬屬性。

監控的目的是爲了防止 myData 的屬性變了,但 vm 卻不知道。如果實現監控的話,只要 myData 的屬性改變都會被 vm 監聽到,然後就可以調用 render(data),將數據在視圖層進行重新渲染,實現數據的響應式。

  • Vue 的 options.data 在傳入 Vue 後,會被 vue 監聽,data 會被篡改,本來的 n 變成 get(n) set(n)
  • Vue 的 options.data 在傳入 Vue 後,會被 vue 實例代理,const vm = new Vue(),vm 就是 options.data 的代理
  • options.data 的所有讀寫都會被 Vue 監控,不管是對 data 本身,還是它的代理都會被監控,vue會在data變化時更新UI
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章