Vue3 Proxy代理为什么要用 Reflect映射

本文深入探讨Vue3中基于Proxy的响应式原理,解释createGetter和createSetter的作用,并详细分析了Proxy与Reflect的关系。通过示例说明了Reflect.get和Reflect.set在响应式数据追踪中的关键作用,揭示了在访问器属性场景下,为何需要使用Reflect.get来确保响应式的正确工作。
摘要由CSDN通过智能技术生成

在这里插入图片描述

瞅一眼Vue3源码

地址:https://github.com/vuejs/core/blob/main/packages/reactivity/src/baseHandlers.ts

可以看到Proxy响应式代理 依赖 createGettercreateSetter方法:

🚥 createGetter

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
  	// 代码忽略...
    const res = Reflect.get(target, key, receiver)
    // ....
    return res
  }
}

🚥 createSetter

function createSetter(shallow = false) {
  return function set(target: object, key: string | symbol,value: unknown, receiver: object): boolean {
    // 代码忽略...
    const result = Reflect.set(target, key, value, receiver)
    // ....
    return result
  }
}

理解 Proxy 和 Reflect

   既然 Vue3 的响应式数据是基于 Proxy 实现的,那么我们就有必要了解Proxy 以及与之关联的Reflect。什么是 Proxy 呢?简单地说,使用 Proxy 可以创建一个代理对象。它能够实现对其他对象的代理,这里的关键词是其他对象,也就是说,Proxy 只能代理对象,无法代非对象值,例如字符串、布尔值等。那么,代理的是什呢?所谓代理,指的是对一个对象基本语义的代理。它允许我们拦截重新定义对一个对象的基本提作。

什么是基本语义?给出一个对象obj ,可以对它进行一些操作。例如读取属性值、设置属性值:

obj.foo // 读取foo的值
obj.foo++ // 读取和设置foo的值

类似这种读取、设置属性的值的操作,就属于基本语义的操作,既然是基本操作,那么他就可以使用Proxy拦截:

const p = new Proxy(obj, {
	// 拦截读取属性
	get(){
	.......
	},
	// 拦截设置属性
	set(){
	.......
	}
})

在JavaScript的世界里,万物皆对象。例如一个函数也是一个对象,所以调用函数也是对一个对象基本的操作:

const fn = name=>{
	console.log('name==>',name);
}
	
const p = new Proxy(fn,{
	apply(target,thisArg,argArray){
		target.call(thisArg,...argArray)
	}
})
	
p('大宝') //输出 name⇒ 大宝

  上面两个例子说明了什么是基本操作。Proxy 只能够拦截对一个对象的基本操作。那么,什么是非基本操作呢?其实调用对象下的方法就是典型的非基本操作,我们叫它复合操作:

	obj.fn()

   实际上,调用一个对象下的方法,是由两个基本语义组成的。第一个基本语义是 get,即先过get操作得到 b.fn属性。第个基本诺义是函数调用,即通过 get 得到 obj.fn 的值后再调用它,也就是我们上面说到的 apply

了解了proxy,我们再来讨论Reflect。Reflect 是一个全局对象,其下有许多方法,例如:

Reflect.get();
Reflect.set();
Reflect.apply();

  可能已经注意到了,Reflect 下的方法与 Proxy 的拦截器方法名字相同,其实这不是何偶然,任何 在Proxy 的拦截器中够找到的方法,都能够在 Reflect 中找到同名函数,那么这些作用是什么呢?其实它们的作用一点儿都不神秘。拿 Reflect.get 函数来说,它的功能就是了访问一个对象属性的默认行为,例如下面两个操作是等价的:

const obj = { foo: 1 }
 console.log(obj.foo) //1 直接读取
 console.log(Reflect.get(obj, 'foo')) //1 使用 Reflect.get 读取

Proxy和Relect有什么区别呢?Reflect.get函数还能接收第三个参数者receiver ,可以理解为函数调用过程中的this。

const obj = {
    name:'阳了',
    get foo(){
        return this.name;
    }
}
console.log(Reflect.get(obj, 'foo', {name: '阴了'})) // 打印出 '阴了' 而不是 '阳了'

  在上段代码中,我们指定第三个参数 recever 为一个对象 {name:“阴了”} ,这时读取到的 receiver 对象的 foo 属性值。实际上, Reflect.* 方法还有很多其他方面的意义,我们只谈论这一点因为它与响应式的实现密切相关。

如果不用Reflect进行映射:

const obj = {foo:1};
const p = new Proxy(obj,{
	get(target,key){
		track(target,key);
		reutrn target[key]
	}
	set(target,key,newVal){
		target[key] = newVal;
		trigger(target,key)
	}
})

这段代码有什么问题吗?我们借助 effect 让问题暴露出来。首先修改一下obj对象,为它添加bar属性:

const obj = {
	foo:1,
	get bar (){
		return this.foo;
	}
}

可以看到,bar属性是一个访问器属性,它返回了this.foo属性的值。在effect副作用函数中通过代理对象p访问bar属性:

effect(()=>{
	console.log(p.bar) // 1
})

我们来分析一下这个过程发生了什么:

  1. 当 effect 注册的副作用函数执行时,会读取 p.bar 属性,它发现p.bar是一个访问器属性,因此执行 getter 函数。
  2. 由于在 getter 函数中通过 this.foo读取了foo属性值,因此我们认为副作用函数与属性 foo 之间也会建立联系。
  3. 当我们修改 p.foo的值时应该能够触发响应,使得副作用函数重新执行才对。

然而实际并非如此,当我们尝试修改p.foo 的值时:

p.foo++

副作用函数并没有重新执行,问题出在哪里呢?

实际上,问题就出在 bar 属性的访问器函数 getter 里:上面代码中的return this.foo; this指向哪里?

  1. 我们回顾一下整个流程首先,我们通过代理对象,访问 p.bar,这会触发代理对象的 get 拦截函数执行
  2. 在get被拦截函数内,通过 target[key] 返回属性值。其中 target 是原始对象 obj,而key 就是字符串‘bar’, 所以target[key] 相当与obj.bar。因此,当我们使用p.bar访问bar属性时,它的getter函数内的this指向的其实时原始对象obj
  3. 这说明我们最终访问的是obj.foo.很显然,在 副作用函数 内通过原始对象访问它的某个属性是不会建立响应式联系的,这等价于:
effect(()=>{
	// obj 是原始数据,不是代理对象,这样的访问不能够建立响应联系
	ob.foo
})

因为这样做不会建立响应式联系,就出现无法触发响应的问题,这时Reflect.get就派上用场了:


const p = new Proxy(obj,{
	get(target,key){
	// 收集依赖
	track(target,key);
	// 使用Reflect.get返回读取带的属性的值
	reutrn Reflect(target,key,receiver)
	}
	......
}
})

当我们使用代理对象 p访问 bar 属性时,那么 receiver 就是 p,你可以把它简单地理解数调用中的 this

接着关键的一步发生了,我们使用 RefLect.get(target, key, receiver)代替之前的 target[key] ,这的关键点就是第三个参数 receiver。我们已经知道它就是代理象所以访问器属性 bar 的 getter 函数内的 this 指向代理对象 p:

const obj = {
	foo:1,
	get bar(){
	//这里的this指向代理对象p
	return this.foo;
	}
}

  可以看到,this由原始对象变成了代理对象 p。 很显然,这会在在副作用函数与响应式数据之间建立响应联系,从而达到收集依赖的效果。如果此时再对p.foo进行自增操作。会发现已经能够触发副作用函数重新执行了!


在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@才华有限公司

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值