目錄
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,對象屬性可以是一個 函數、getter、setter 方法。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