浅聊一下
在上一篇文章(到底该用ref还是reactive???)中,我们说到ref在处理对象类型和reactive处理复杂数据的时候,返回的是一个Proxy的实例化对象,在本篇文章中,我们将来详解Vue3到底是如何基于Proxy实现响应式的…
Proxy
我们先来看看阮一峰老师对于Proxy的描述
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
可能不是那么通俗易懂,我们上代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
let person = {
name: '张三',
age: 18,
sex:'男'
}
let p = new Proxy(person,{})
</script>
</body>
</html>
Proxy需要接收两个参数const p = new Proxy(target, handler)
-
要使用
Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 -
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理
p
的行为。
我们在上述例子中传入了一个person对象,另一个参数操作暂不填写,在这里p就是我们的代理对象,当我们要对person进行增删改查的时候,p就会拦截下来,然后进行handler
中的操作…
我们在控制台进行如下操作:
可以看到当我们对p的值进行操作的时候,person对象的值也会改变,那么我们的响应式实现原理就到此结束了吗?还没有!我们只是通过代理修改了person的内容而已,要实现响应式,还得对修改进行捕获…
响应式实现
在Proxy中我们提到,接收的第二个参数Handler对象中存储的是要进行的各种代理操作,那么我们将在里面对修改进行一个捕获…
let p = new Proxy(person,{
get(target,prop){
console.log(`读取了p上的${prop}属性`,target,prop);
return target[prop]
},
set(target,prop,value){
console.log(`修改了p上的${prop}属性`,target,prop,value);
target[prop] = value
},
deleteProperty(target,prop){
console.log(`删除了p上的${prop}属性`,target,prop);
return delete target[prop]
}
})
-
get
:- 当你尝试获取代理对象
p
上的属性时(例如p.name
),这个拦截器会被调用。 - 它接收两个参数:
target
表示目标对象(这里是person
),prop
表示要获取的属性名(例如"name"
)。 - 这个拦截器会输出一条日志,说明你在读取代理对象
p
上的哪个属性,并返回目标对象上相应属性的值。
- 当你尝试获取代理对象
-
set
:- 当你尝试设置代理对象
p
上的属性时(例如p.age = 35
),这个拦截器会被调用。 - 它接收三个参数:
target
表示目标对象(这里是person
),prop
表示要设置的属性名(例如"age"
),value
表示要设置的值(例如35
)。 - 这个拦截器会输出一条日志,说明你在修改代理对象
p
上的哪个属性,并在目标对象上设置相应的属性值。
- 当你尝试设置代理对象
-
deleteProperty
:- 当你尝试删除代理对象
p
上的属性时(例如delete p.age
),这个拦截器会被调用。 - 它接收两个参数:
target
表示目标对象(这里是person
),prop
表示要删除的属性名(例如"age"
)。 - 这个拦截器会输出一条日志,说明你在删除代理对象
p
上的哪个属性,并在目标对象上删除相应的属性。
- 当你尝试删除代理对象
来看看效果:
将增删改查全部调用一遍,发现捕获了操作并且实现了响应式
但是,Vue3响应式的底层这么写有一点low了,vue3使用到了Reflect
let p = new Proxy(person,{
get(target,prop){
console.log(`读取了p上的${prop}属性`,target,prop);
return Reflect.get(target,prop)
},
set(target,prop,value){
console.log(`修改了p上的${prop}属性`,target,prop,value);
return Reflect.set(target,prop,value)
},
deleteProperty(target,prop){
console.log(`删除了p上的${prop}属性`,target,prop);
return Reflect.deleteProperty(target,prop)
}
})
在真正的响应式原理中,通常会使用 Reflect
对象来操作目标对象,而不是直接操作目标对象本身。这样做有几个优点:
- 更加规范和易读: 使用
Reflect
提供的方法,如Reflect.get
、Reflect.set
、Reflect.deleteProperty
,使代码更符合规范,易于阅读和理解。这些方法的名称清晰地表明了它们的功能,使代码更易于维护和修改。 - 更安全:
Reflect
方法的使用会使代码更安全,因为它们提供了一种标准的方式来执行基本操作,避免了直接操作目标对象可能导致的一些潜在问题。例如,Reflect.set
方法会返回一个布尔值,指示属性是否成功设置,而直接设置属性时,你必须自己处理设置是否成功的情况。 - 拓展性: 使用
Reflect
提供的方法,使得代码更具有拓展性。你可以在这些方法的基础上自定义更复杂的行为,而不会影响到Proxy
的行为。 - 一致性:
Reflect
方法的行为与语言内部操作的行为一致,这使得代码更一致和可预测。与直接操作目标对象不同,Reflect
方法在处理特殊情况时也会返回一致的结果。
结尾
说到这里,vue3响应式实现原理你就应该会了…
原文链接:https://juejin.cn/post/7334623638115598347 作者:滚去睡觉