最近研究vue3源码, 知道使用了Proxy和Reflect, 但是不了解它们之间的关系
这篇文章主要是让大家了解vue3为什么使用Proxy和Reflect以及响应式的部分原理
为什么使用Proxy(Proxy和Object.defineproperty)
Object.defineproperty实现对象监听
先解析一下vue2中使用的Object.defineproperty
let obj = {
a: 10
}
Object.keys(obj).forEach(key => {
let value = obj[key]
Object.defineProperty(obj, key, {
set(newValue) {
console.log(`监听${key}改变: ${newValue}`);
value = newValue
},
get() {
console.log(`获取${key}对应的值: ${value}`);
return value
}
})
})
obj.a = 100 // 监听a改变: 100
obj.b = 10 // 不会被监听到
通过上面的例子我们可以看到obj新添加的属性b, 并不会被监听到
vue2中使用中我们也会遇到这样的问题
# template
{{ obj.a }}
{{ obj.b }}
# 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;
}
}
我们发现点击obj.a是响应式, 页面也会更新
而新增的obj.b点击则不会
因为vue2使用的Object.defineproperty无法监听到新增的对象属性
针对这个问题vue2提供了$set方法来解决
mounted () {
this.$set(this.obj, "b", 1)
}
Proxy实现对象监听
let obj = {
a: 10
}
const handler = {
get(target, prop) {
console.log(`获取${prop}对应的值: ${target[prop]}`);
return target[prop];
},
set(target, prop, val) {
target[prop] = val;
console.log(`监听${prop}改变: ${val}`);
return true
}
}
let obj2 = new Proxy(obj, handler)
obj2.b = 100 // 监听b改变: 100
我们可以看到通过Proxy实例可以对新添加的属性进行监听
当然Proxy还可以做许多的其他功能, 这里就不多介绍了
我查看Vue3的源码的时候一直对Proxy中使用的Reflect感到不解,为什么要使用Reflect.get和Reflect.set, 我查询了一些文章, 大概了一下思路
Reflect
我将通过一些问题, 来指明Reflect中Proxy中的用处
我们有一个user带有_name属性的对象和一个吸气剂。
这是围绕它的代理:
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
对于我们的示例而言,这就足够了。
一切似乎都还好。但是,让我们将示例变得更加复杂。
继承另一个对象后admin从user,我们可以观察到不正确的行为:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
console.log(target) // user对象{_name: "Guest"}
return target[prop];
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
console.log(admin.name); // Guest
阅读admin.name应该返回"Admin",而不是"Guest"!
怎么了?也许我们在继承方面做错了什么?
问题实际上出在代理所在的行中:
- 当我们阅读时admin.name,由于admin对象没有自己的属性,搜索将转到其原型。
- 原型是userProxy
- name从代理读取属性时,其get将触发并从原始对象中返回该属性,它在上下文中运行其代码
this=target
。因此,结果this._name
来自原始对象target
,即:fromuser
。
而这个时候就是Reflect.get就派上用场了
如果我们使用它,一切都会正常运行。
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
Reflect.get中receiver参数,保留了对正确引用this(即admin)的引用,该引用将Reflect.get中正确的对象使用传递给get。