共计 2406 个字符,预计需要花费 7 分钟才能阅读完成。
最近钻研 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 | |
<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;} | |
} |
咱们发现点击 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。
相干文章
https://javascript.info/proxy