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 ["EN", "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, 'n', {
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, 'n', { //覆蓋原來的data.n屬性
get(){
return _n
},
set(newValue){
if(newValue<0)return
_n = newValue
}
})// 改變data{}數據對象本身屬性,可通過閉包形成上下文,讓原來的實際屬性值存在閉包的上下文_n中
const obj = {}
Object.defineProperty(obj, 'n', {
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','m','10')//為vm的data對象添加屬性m值為10
3.數組變異
詳細見 vuejs官網
對於數組的數據增加,無法控制其新增個數因此不能提前聲明所有數據值,而一個一個 set 又太麻煩,而且數組是常用的對象數據類型中的一種,因此 vue
的作者就對數組的增刪函數如 push
和 pop
等進行了篡改,用戶在使用 vue
中數組增刪時仍是用 push
和 pop
,但是里面進行了額外的處理,這幾個被篡改的 API 會對數組新增是數據代理監聽並根據數據響應改變 UI 視圖。