Vue 源碼解析(Reflect and Proxy)
最近研究 vue3 源碼, 知道使用了 Proxy 和 Reflect, 但是不了解它們之間的關(guān)系
這篇文章主要是讓大家了解 vue3 為什么使用 Proxy 和 Reflect 以及響應(yīng)式的部分原理
為什么使用 Proxy(Proxy 和 Object.defineproperty)
Object.defineproperty實(shí)現(xiàn)對(duì)象監(jiān)聽(tīng)
先解析一下vue2中使用的Object.defineproperty
let obj = {
a: 10
}
Object.keys(obj).forEach(key => {
let value = obj[key]
Object.defineProperty(obj, key, {
set(newValue) {
console.log(`監(jiān)聽(tīng)${key}改變: ${newValue}`);
value = newValue
},
get() {
console.log(`獲取${key}對(duì)應(yīng)的值: ${value}`);
return value
}
})
})
obj.a = 100 // 監(jiān)聽(tīng)a改變: 100
obj.b = 10 // 不會(huì)被監(jiān)聽(tīng)到
通過(guò)上面的例子我們可以看到obj新添加的屬性b, 并不會(huì)被監(jiān)聽(tīng)到
vue2中使用中我們也會(huì)遇到這樣的問(wèn)題
# template
<p @click="adda(obj)">{{ obj.a }}</p>
<p @click="addb(obj)">{{ obj.b }}</p>
# srcript
data () {
return {
obj:{
a:1
}
}
},
mounted () {
this.obj.b = 1;
},
methods: {
addb(item){
item.b += 1;
console.log(this.obj.b)
},
adda(item){
item.a += 1;
}
}
我們發(fā)現(xiàn)點(diǎn)擊obj.a是響應(yīng)式, 頁(yè)面也會(huì)更新
而新增的obj.b點(diǎn)擊則不會(huì)
因?yàn)関ue2使用的Object.defineproperty無(wú)法監(jiān)聽(tīng)到新增的對(duì)象屬性
針對(duì)這個(gè)問(wèn)題vue2提供了$set方法來(lái)解決
mounted () {
this.$set(this.obj, "b", 1)
}
Proxy實(shí)現(xiàn)對(duì)象監(jiān)聽(tīng)
let obj = {
a: 10
}
const handler = {
get(target, prop) {
console.log(`獲取${prop}對(duì)應(yīng)的值: ${target[prop]}`);
return target[prop];
},
set(target, prop, val) {
target[prop] = val;
console.log(`監(jiān)聽(tīng)${prop}改變: ${val}`);
return true
}
}
let obj2 = new Proxy(obj, handler)
obj2.b = 100 // 監(jiān)聽(tīng)b改變: 100
我們可以看到通過(guò)Proxy實(shí)例可以對(duì)新添加的屬性進(jìn)行監(jiān)聽(tīng)
當(dāng)然Proxy還可以做許多的其他功能, 這里就不多介紹了
我查看Vue3的源碼的時(shí)候一直對(duì)Proxy中使用的Reflect感到不解,為什么要使用Reflect.get和Reflect.set, 我查詢了一些文章, 大概了一下思路
Reflect
我將通過(guò)一些問(wèn)題, 來(lái)指明Reflect中Proxy中的用處
我們有一個(gè)user帶有_name屬性的對(duì)象和一個(gè)吸氣劑。
這是圍繞它的代理:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
return target[prop];
}
});
console.log(userProxy.name); // Guest
對(duì)于我們的示例而言,這就足夠了。
一切似乎都還好。但是,讓我們將示例變得更加復(fù)雜。
繼承另一個(gè)對(duì)象后admin從user,我們可以觀察到不正確的行為:
llet user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
console.log(target) // user對(duì)象{_name: "Guest"}
return target[prop];
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
console.log(admin.name); // Guest
閱讀admin.name應(yīng)該返回"Admin",而不是"Guest"!
怎么了?也許我們?cè)诶^承方面做錯(cuò)了什么?
問(wèn)題實(shí)際上出在代理所在的行中:
當(dāng)我們閱讀時(shí)admin.name,由于admin對(duì)象沒(méi)有自己的屬性,搜索將轉(zhuǎn)到其原型。 原型是userProxy name從代理讀取屬性時(shí),其get將觸發(fā)并從原始對(duì)象中返回該屬性,它在上下文中運(yùn)行其代碼this=target。因此,結(jié)果this._name來(lái)自原始對(duì)象target,即:from user。
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) { // receiver = admin
return Reflect.get(target, prop, receiver);
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
console.log(admin.name); // Admin

