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

news2024/11/17 0:51:52

在这里插入图片描述

瞅一眼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进行自增操作。会发现已经能够触发副作用函数重新执行了!


在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/107809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go并发大坑:inconsistent mutex state 解决及心得体会

文章目录前言首次排查(未重视)问题逐渐严重加大排查力度增加锁日志race detector一次意外的复现写在最后解决问题所必需的品格前言 大概在这篇文章发布数个月之前,某天,线上稳定运行的Go服务突然毫无征兆的发生了一次重启&#x…

MyBatis的相应API与传统和代理开发的Dao层实现

MyBatis的相应API 1、SqlSession工厂构建器SqlSessionFactoryBuilder 常用API:SqlSessionFactory build(InputStream inputStream) 通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象 其中,mybatis-config.xml是类加载器的路径&…

为什么要学编程?为什么是Python?怎么学?

今天我们聊了为什么要去学编程,学编程为什么要选择Python。上大学的时候我学的信息管理,毕业后选择了和网络相关的网络安全公司做售后。 从上学的时候自己就想过去学学编程,感觉他们敲代码都敲的那么酷,看着满屏的代码还有命令行…

2022广航蓝桥杯选拔赛压轴题:取数博弈游戏

原题链接 对于这种「判断先手后手的必胜必败」的题目,博弈论方向是一个优先考虑的方向。 博弈论的重要思想就是决策者都要做出全局最优的决策而非局部最优,就好比专业的棋手走一步看五步,而博弈论则要求棋手走一步看无限步。 解题思路 凡…

[洛谷]P3613 【深基15.例2】寄包柜

[洛谷]P3613 【深基15.例2】寄包柜一、问题描述题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示二、思路分析1、算法标签2、算法思路三、代码实现一、问题描述 [洛谷]P3613 【深基15.例2】寄包柜 题目描述 超市里有 n(1≤n≤105)n(1\le n\le10^5)n(1≤n≤105) 个…

《Quarkus实战》总结

《Quarkus实战》总结 目录 一、优势二、搭建脚手架三、Rest 1)启用跨源资源共享2)拦截HTTP请求3)使用SSL进行安全连接 四、配置 1)以程序化的方式访问配置属性2)在外部覆盖配置值3)修改日志配置 五、编程模型 1)校验输入值和输出值2)全局异常处理3)创建自定义校验4)以程序化的方…

荣耀加冕 大道可期 | WotoKOL卧兔荣获星球奖BrandStar Awards 2022服务创新奖

2022年11月,星球奖BrandStar Awards 2022获奖名单揭晓,WotoKOL卧兔荣获服务创新奖 ! 据了解,星球奖BrandStar Awards 是品牌星球于 2021 年发起的,以「品牌创新」为核心指标的奖项。星球奖系统囊括品牌建设的核心板块&…

【C语言】你知道程序是如何调用函数的吗?

目录 1.函数栈帧的含义 概念 要用到的汇编语言的知识 示例 2.理解栈帧 2.1 main函数栈帧的创建 2.2 局部变量的创建 2.3 函数传参 2.4 调用函数 2.5 函数返回 一个.c文件在调用函数的时候(包括main 函数),其内存中的栈区有什么变…

祝贺誉天多位学员通过HCIE-Datacom,喜提誉天助跑奖学金

华为HCIE-Datacom自发布以来,就一直牵动着数通学员们的心。不论是版本之间的取舍,还是新增模块的学习,都一度引发热议。为了帮助各位小伙伴完成过渡顺利通过考试,誉天除了在师资、设备、教研等方面下足功夫之外,还专门…

12. 【gRPC系列学习】失败重试Retry原理

本节分析gRPC的失败重试机制,从原理、源码上进行深入分析,下节编写测试代码,验证Retry机制。 官方文档在失败重试方面的讲解比较透彻,下面内容是对官方文档的整理、归类、总结。 1. 两种重试策略简介 1)retry策略 retry策略:发送RPC,服务端应用层返回指定的异常码后,…

ES6--》了解并使用模块化规范

目录 ES6模块化规范 模块化的分类 ES6模块化的语法 ES6模块化规范 ES6模块化规范是浏览器端与服务器端通用的模块化开发规范。它的出现极大的降低了前端开发者的模块化学习成本,开发者不需要再额外的学习AMD、CMD或CommonJS等模块化规范。 ES6模块化规范定义&am…

激活海量数据价值,实现生产过程优化

背景 在全球掀起的新一轮工业转型浪潮中,智能制造面临巨大发展机遇。得益于云计算、大数据和人工智能技术的加持,工业转型升级进入新阶段,人们逐渐意识到由数据驱动催生的新商业模式所带来的巨大价值,数据和算法模型的结合与碰撞…

KMP算法,686. 重复叠加字符串匹配,

首先,要明白,如果b串的第一个字母b[0]在a串中没有找到,那么不管a串复制多少次,b串都不会是a的子串。 如果b串的第一个字母b[0]在a串中能找到,那么我们看一下a串需要复制几次呢? 总结,可以发现这四种情况是…

数据库系统概论第六章(关系数据理论)知识点总结(3)—— 范式知识点总结

本专栏收录了数据库的知识点,而从本文起,将讲述有关于关系数据理论中的第一范式、第二范式、第三范式以及BC范式有关知识点,提供给有需要的小伙伴进行学习,本专栏地址可以戳下面链接查看 🎈 数据库知识点总结&#xff…

windows CMD “ZEBAR ZPL 命令打印“驱动打印机

一、命令预览 copy WS20_8891898136BB520221221003.txt "\\10.165.98.33\IP-print-ZDesigner ZT411-203dpi ZPL" 二、难点打印机名称 1. copy 命令必须为共享打印机,及打印机共享名称,可以参考(163条消息) 发送ZPL指令到斑马打印机&#xff…

【Java对象拷贝机制】「实战开发专题」高性能使用CGlib实现Bean拷贝(BeanCopier)指南

对象拷贝现状 业务系统中经常需要两个对象进行属性的拷贝,不能否认逐个的对象拷贝是最快速最安全的做法,但是当数据对象的属性字段数量超过程序员的容忍的程度,代码因此变得臃肿不堪,使用一些方便的对象拷贝工具类将是很好的选择…

训练seq2seq模型的一些Tips——李宏毅机器学习笔记

Copy Mechanism 有时我们并不需要decoder创造一些东西出来,有些内容是可以从encoder复制而来。 最早具有复制能力的模型:Pointer Network 例如: chat-bot Summarization 至少要训练百万篇文章 Guided Attention Monotonic Attention Lo…

PPT怎么转化成PDF?这两种方法教你快速解决!

今天我们将向您介绍如何将 PowerPoint 演示文稿 (PPT) 转换为 PDF 文件。 我们将通过两种方法来做到这一点: 将 PowerPoint 演示文稿另存为 PDF 文件。使用 奇客免费PDF转换 将 PowerPoint 演示文稿转换为 PDF。方法一:使用 Microsoft PowerPoint 将 P…

合理利用chatGpt之新冠阳性

🏆今日学习目标: 🍀合理利用chatGpt之新冠阳性 ✅创作者:林在闪闪发光 ⏰预计时间:30分钟 🎉个人主页:林在闪闪发光的个人主页 🍁林在闪闪发光的个人社区,欢迎你的加入:…

AI 预测到了正确的世界杯胜利者吗 ?

在过去的一个月里,卡塔尔 2022 年世界杯让我们目睹了一个又一个令人震惊的结果,甚至在决赛之前,让所有人的目光都聚焦在法国和阿根廷两支球队上,其实在球迷们心中,早就开始了各种猜测。😄现在是时候回顾一下…