Vue數(shù)據(jù)響應(yīng)式主要研究的是 Vue 構(gòu)造選項(xiàng)中 data 屬性的特性
深入響應(yīng)式 官方文檔 網(wǎng)址: https://cn.vuejs.org/v2/guide/reactivity.html
1. 首先理解 ES6 的 getter 與 setter 語法
obj = { // 首先創(chuàng)建一個(gè)普通對(duì)象
name:'小歐',
age:18,
姓名(){
return this.name;
}
}
console.log(obj.姓名()); // 小歐
// 如何使 obj.姓名 不帶括號(hào)也可以得到 name 值呢荒勇?
// 使用ES6新語法
obj = {
name:'小歐',
age:18,
get 姓名(){ // get (讀) 一個(gè)值時(shí)執(zhí)行的函數(shù) 獲取時(shí)無需使用()執(zhí)行
return this.name;
}
}
console.log(obj.姓名); // 小歐
obj = {
name:'小歐',
age:18,
get 姓名(){
return this.name;
},
set 姓名(set_name){ // 同樣 set (寫) 一個(gè)值得時(shí)候也能執(zhí)行相應(yīng)的函數(shù)
this.name = set_name + '你好'
}
}
console.log(obj.姓名); // 小歐
obj.姓名 = '高圓圓'
console.log(obj.name) // 高圓圓你好
2.再看 data 屬性
當(dāng)我們?cè)趘ue實(shí)例創(chuàng)建之前定義一個(gè)數(shù)據(jù)對(duì)象,在沒有經(jīng)過 vue 實(shí)例化之前,是一個(gè)正常的對(duì)象
let tata = {
n:0
}
console.log(tata) // n:0 __proto__: Object
當(dāng)我們使用該對(duì)象充當(dāng) Vue 實(shí)例的data屬性之后
new Vue({
// components:{Demo},
template:`
<div>
<button @click="add"></button>
</div>
`,
methods:{
des(){
this.visible = !this.visible
},
fn(){
console.log('調(diào)用了fn')
},
add(){
this.n++;
}
},
data:tata // 把自定義的對(duì)象充當(dāng) data 使用
}).$mount('#app')
console.log(tata) // 再次打印 tata 對(duì)象 如下圖
數(shù)據(jù) n 此時(shí)是兩個(gè)函數(shù)的名字 通過 n 的獲取或者賦值可以執(zhí)行相應(yīng)的函數(shù)投储,n:(...) 表示實(shí)際上不存在 n 這個(gè)屬性了 只不過可以通過 get set 來模擬對(duì) n 的取值 和賦值根暑,在取值和賦值時(shí)完成更加靈活的操作(比如更新視圖)
Object.defineProperty() 方法
該方法會(huì)直接在一個(gè)對(duì)象上定義一個(gè)新屬性舵抹,或者修改一個(gè)對(duì)象的現(xiàn)有屬性榜贴,并返回此對(duì)象矢赁。
const object1 = {};
Object.defineProperty(object1, 'property1', {
value: 42, // 表示此屬性值 為 42
writable: false // 表示此屬性值 不可寫
});
object1.property1 = 77;
// throws an error in strict mode
console.log(object1.property1);
// expected output: 42
還可以設(shè)置 getter setter 屬性
ojb3 = {
name:'小歐',
age:18
}
let _look = null;
Object.defineProperty(ojb3,'look',{
get(){
return _look
},
set(value){
_look = value
}
})
ojb3.look = '好看'
console.log(ojb3.look)
總結(jié):關(guān)于 Object.defineProperty() 方法
- 可以給對(duì)象添加屬性value
- 可以給對(duì)象添加getter / setter
- getter / setter用于對(duì)屬性的讀寫進(jìn)行監(jiān)控
在 Vue 實(shí)例創(chuàng)建的時(shí)候糯笙,Vue會(huì)將 data 數(shù)據(jù)變?yōu)樘砑恿薵etter setter 的數(shù)據(jù)屬性
3.模擬 vue 的數(shù)據(jù)代理原理
需求一:
// 需求1:使用Object.defineProperty 定義 n
let data1 = {} // 空對(duì)象
Object.defineProperty(data1,'n',{
value:0
})
console.log(data1.n) // 0
需求二:
// 需求二 n 的值不能被賦值為小于零的數(shù)
// 比如 data2.n = 1 有效 data2.n = -1 無效
let data2 = {}
data2._n = 0 // 使用_n來儲(chǔ)存n的值
Object.defineProperty(data2,'n',{
get(){
return this._n
},
set(value){
if(value < 0) return
this._n = value
}
})
data2.n = -1
console.log(data2.n); // 0 因?yàn)?1的賦值無效
data2.n = 1
console.log(data2.n); // 1 大于0有效
但是需求二中 如果直接修改 data2._n 屬性也是無法攔截的,因此還要改進(jìn)
需求三:使用代理模式
// 需求三:使用代理
let data3 = proxy({data:{n:0}})
function proxy({data} /* 解構(gòu)賦值 */){
const obj = {}
// 這里的n理論上應(yīng)該遍歷data的所有key,這里簡化了
Object.defineProperty(obj,'n',{
get(){
return data.n
},
set(value){
if(value < 0) return
data.n = value
}
})
return obj // obj就是代理對(duì)象
}
data3.n = -1 // 這里觸發(fā)了data3 的 setter 函數(shù) 不會(huì)賦值為負(fù)數(shù)
console.log(data3.n) // 0
上面這個(gè)代理如果 proxy函數(shù)的參數(shù)不是匿名對(duì)象那還是會(huì)被修改值
需求四:使用代理的加強(qiáng)版
// 需求五 用戶修改原始對(duì)象也能攔截
let myData = {n:0}
let data5 = proxy({data:myData})
function proxy({data}){
// 這里的n理論上應(yīng)該遍歷data的所有key,這里簡化了
let value = data.n
delete data.n // 這行可以不寫 因?yàn)橄旅鎰?chuàng)建的n屬性會(huì)被覆蓋
Object.defineProperty(data,'n',{
get(){
return value
},
set(newValue){
if(newValue < 0)return
value = newValue
}
})
// 上面這幾句會(huì)監(jiān)聽 data 對(duì)象數(shù)據(jù)的變化
const obj = {}
Object.defineProperty(obj,'n',{
get(){
return data.n
},
set(){
if(value < 0)return
data.n = value
}
})
return obj
}
myData.n = -5
console.log(myData.n); // 0 被監(jiān)聽攔截
data5.n = -6
console.log(data5.n); // 0 被代理攔截
綜上所述撩银,vue實(shí)例創(chuàng)建時(shí)對(duì) data 的篡改就包含了這個(gè)原理
let data5 = proxy({data:myData}) // 類似于
const vm = new Vue(data:{n:0}) // Vue對(duì)于數(shù)據(jù)的篡改原理正是上面解釋的那樣
小結(jié):
vm = new Vue(data:{n:0})
// Vue會(huì)做出如下幾件事
- 會(huì)讓 vm 成為myData的代理 (proxy)
- 會(huì)對(duì)myData的所有屬性進(jìn)行監(jiān)控
- 監(jiān)控的目的就是在myData數(shù)據(jù)更改的時(shí)候给涕,通知vm實(shí)例對(duì)象從而調(diào)用render方法更新視圖
- 這便是Vue的看家本領(lǐng)
簡言之就是 Vue通過 Object.defineProperty 對(duì)實(shí)例對(duì)象中data進(jìn)行添加 getter / setter 從而用來對(duì)屬性的讀寫進(jìn)行監(jiān)控,從而在data數(shù)據(jù)變化時(shí)通知Vue實(shí)例進(jìn)行視圖的更新
4.Vue data屬性存在的問題
-
Object.defineProperty的問題
Object.defineProperty(obj, 'n',{...}) // 必須要有一個(gè) n 屬性才能監(jiān)聽
如果沒有寫 n 的話 Vue會(huì)給出警告额获,無法監(jiān)聽 后面添加的屬性
new Vue({
// components:{Demo},
template:`
<div>
{{obj.b}}
<button @click="add">點(diǎn)擊</button>
</div>
`,
methods:{
add(){
this.obj.b++ // 變量 b 并沒有提前寫好 頁面中就不會(huì)顯示
}
},
data:{
obj:{
a:0 // obj.a 會(huì)被Vue監(jiān)聽 & 代理
}
}
}).$mount('#app')
解決辦法:
使用 Vue.set() 方法的作用
- 新增 key
- 自動(dòng)創(chuàng)建代理和監(jiān)聽(如果沒有創(chuàng)建過)
- 觸發(fā)視圖更新(但并不會(huì)立刻更新)
new Vue({
// components:{Demo},
template:`
<div>
{{obj.b}}
<button @click="add">點(diǎn)擊</button>
<button @click="add2">點(diǎn)擊</button>
</div>
`,
methods:{
add(){
Vue.set(this.obj,'b',1) // 使用Vue.set()方法添加監(jiān)聽屬性
},
add2(){ // 點(diǎn)擊按鈕時(shí) 會(huì)更新視圖
this.obj.b++
}
},
data:{
obj:{
a:0, // obj.a 會(huì)被Vue監(jiān)聽 & 代理
b:undefined
}
}
}).$mount('#app')
-
數(shù)組的篡改該方法
new Vue({
// components:{Demo},
template:`
<div>
{{obj.array}}
<button @click="add">點(diǎn)擊</button>
</div>
`,
methods:{
add(){
this.obj.array.push('d')
console.log(this.obj.array)
}
},
data:{
obj:{
array:['a','b','c']
}
}
}).$mount('#app')
打印結(jié)果:
為了防止每次修改數(shù)組都需要給后添加的數(shù)組項(xiàng)使用Vue.set()够庙,數(shù)組傳給Vue時(shí),數(shù)組的這七個(gè)方法會(huì)被篡改覆蓋抄邀,文檔中叫做變異方法耘眨,這些方法會(huì)自動(dòng)對(duì)數(shù)組新增項(xiàng)添加對(duì)應(yīng)的監(jiān)聽,并且會(huì)更新視圖
數(shù)組篡改的實(shí)現(xiàn)
所謂的方法篡改實(shí)際上就是在Vue實(shí)例上面加了一層原型鏈境肾,同名的方法會(huì)被最底層的原型覆蓋掉剔难,就實(shí)現(xiàn)了方法篡改
ES6寫法:以 push 方法為例 (模擬實(shí)現(xiàn),并非源碼)
class VueArray extends Array{
push(...arsgs){
const oldLength = this.length // this就是當(dāng)前數(shù)組
super.push(...arsgs)
console.log('我被篡改了')
for(let i = oldLength; i < this.length; i++){
Vue.set(this,i,this[i]) // 將每個(gè)新增的 key 都告訴 Vue 實(shí)例
}
}
}