iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >js中为什么Proxy一定要配合Reflect使用
  • 409
分享到

js中为什么Proxy一定要配合Reflect使用

2024-04-02 19:04:59 409人浏览 安东尼
摘要

目录引言前置知识单独使用 ProxyProxy 中的 receiverReflect 中的 receiver总结结尾引言 EcmaScript 2015 中引入了 Proxy 代理

引言

EcmaScript 2015 中引入了 Proxy 代理 与 Reflect 反射 两个新的内置模块。

我们可以利用 Proxy 和 Reflect 来实现对于对象的代理劫持操作,类似于 Es 5 中 Object.defineProperty()的效果,不过 Reflect & Proxy 远远比它强大。

大多数开发者都了解这两个 es6 中的新增内置模块,可是你也许并不清楚为什么 Proxy 一定要配合 Reflect 使用。

这里,文章通过几个通俗易懂的例子来讲述它们之间相辅相成的关系。

前置知识

  • Proxy 代理,它内置了一系列”陷阱“用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

  • Reflect 反射,它提供拦截 javascript 操作的方法。这些方法与 Proxy 的方法相同。

简单来说,我们可以通过 Proxy 创建对于原始对象的代理对象,从而在代理对象中使用 Reflect 达到对于 JavaScript 原始操作的拦截。

如果你还不了解 & ,那么赶快去 MDN 上去补习他们的知识吧。

毕竟大名鼎鼎的 Vuejs/Core 中核心的响应式模块就是基于这两个 api 来实现的。

单独使用 Proxy

开始的第一个例子,我们先单独使用 Proxy 来烹饪一道简单的开胃小菜:

const obj = {
  name: 'wang.haoyu',
};

const proxy = new Proxy(obj, {
  // get陷阱中target表示原对象 key表示访问的属性名
  get(target, key) {
    console.log('劫持你的数据访问' + key);
    return target[key]
  },
});

proxy.name // 劫持你的数据访问name -> wang.haoyu

看起来很简单对吧,我们通过 Proxy 创建了一个基于 obj 对象的代理,同时在 Proxy 中声明了一个 get 陷阱。

当访问我们访问 proxy.name 时实际触发了对应的 get 陷阱,它会执行 get 陷阱中的逻辑,同时会执行对应陷阱中的逻辑,最终返回对应的 target[key] 也就是所谓的 wang.haoyu .

Proxy 中的 receiver

上边的 Demo 中一切都看起来顺风顺水没错吧,细心的同学在阅读 Proxy 的 MDN 文档上可能会发现其实 Proxy 中 get 陷阱中还会存在一个额外的参数 receiver 。

那么这里的 receiver 究竟表示什么意思呢?大多数同学会将它理解成为代理对象,但这是不全面的。

接下来同样让我们以一个简单的例子来作为切入点:

const obj = {
  name: 'wang.haoyu',
};

const proxy = new Proxy(obj, {
  // get陷阱中target表示原对象 key表示访问的属性名
  get(target, key, receiver) {
    console.log(receiver === proxy);
    return target[key];
  },
});

// log: true
proxy.name;

上述的例子中,我们在 Proxy 实例对象的 get 陷阱上接收了 receiver 这个参数。

同时,我们在陷阱内部打印 console.log(receiver === proxy); 它会打印出 true ,表示这里 receiver 的确是和代理对象相等的。

所以 receiver 的确是可以表示代理对象,但是这仅仅是 receiver 代表的一种情况而已。

接下来我们来看另外一个例子:

const parent = {
  get value() {
    return '19Qingfeng';
  },
};

const proxy = new Proxy(parent, {
  // get陷阱中target表示原对象 key表示访问的属性名
  get(target, key, receiver) {
    console.log(receiver === proxy);
    return target[key];
  },
});

const obj = {
  name: 'wang.haoyu',
};

// 设置obj继承与parent的代理对象proxy
Object.setPrototypeOf(obj, proxy);

// log: false
obj.value

关于原型上出现的 get/set 属性访问器的“屏蔽”效果,我在这篇文章中进行了详细阐述。这里我就不展开讲解了。

我们可以看到,上述的代码同样我在 proxy 对象的 get 陷阱上打印了 console.log(receiver === proxy); 结果却是 false 。

那么你可以稍微思考下这里的 receiver 究竟是什么呢? 其实这也是 proxy 中 get 陷阱第三个 receiver 存在的意义。

它是为了传递正确的调用者指向,你可以看看下方的代码:

...
const proxy = new Proxy(parent, {
  // get陷阱中target表示原对象 key表示访问的属性名
  get(target, key, receiver) {
-   console.log(receiver === proxy) // log:false
+   console.log(receiver === obj) // log:true
    return target[key];
  },
});
...

其实简单来说,get 陷阱中的 receiver 存在的意义就是为了正确的在陷阱中传递上下文。

涉及到属性访问时,不要忘记 get 陷阱还会触发对应的属性访问器,也就是所谓的 get 访问器方法。

我们可以清楚的看到上述的 receiver 代表的是继承与 Proxy 的对象,也就是 obj。

看到这里,我们明白了 Proxy 中 get 陷阱的 receiver 不仅仅代表的是 Proxy 代理对象本身,同时也许他会代表继承 Proxy 的那个对象。

其实本质上来说它还是为了确保陷阱函数中调用者的正确的上下文访问,比如这里的 receiver 指向的是 obj 。

当然,你不要将 revceiver 和 get 陷阱中的 this 弄混了,陷阱中的 this 关键字表示的是代理的 handler 对象。

比如:

const parent = {
  get value() {
    return '19Qingfeng';
  },
};

const handler = {
  get(target, key, receiver) {
    console.log(this === handler); // log: true
    console.log(receiver === obj); // log: true
    return target[key];
  },
};

const proxy = new Proxy(parent, handler);

const obj = {
  name: 'wang.haoyu',
};

// 设置obj继承与parent的代理对象proxy
Object.setPrototypeOf(obj, proxy);

// log: false
obj.value

Reflect 中的 receiver

在清楚了 Proxy 中 get 陷阱的 receiver 后,趁热打铁我们来聊聊 Reflect 反射 API 中 get 陷阱的 receiver。

我们知道在 Proxy 中(以下我们都以 get 陷阱为例)第三个参数 receiver 代表的是代理对象本身或者继承与代理对象的对象,它表示触发陷阱时正确的上下文。

const parent = {
  name: '19Qingfeng',
  get value() {
    return this.name;
  },
};

const handler = {
  get(target, key, receiver) {
    return Reflect.get(target, key);
    // 这里相当于 return target[key]
  },
};

const proxy = new Proxy(parent, handler);

const obj = {
  name: 'wang.haoyu',
};

// 设置obj继承与parent的代理对象proxy
Object.setPrototypeOf(obj, proxy);

// log: false
console.log(obj.value);

我们稍微分析下上边的代码:

  • 当我们调用 obj.value 时,由于 obj 本身不存在 value 属性。

  • 它继承的 proxy 对象中存在 value 的属性访问操作符,所以会发生屏蔽效果。

  • 此时会触发 proxy 上的 get value() 属性访问操作。

  • 同时由于访问了 proxy 上的 value 属性访问器,所以此时会触发 get 陷阱。

  • 进入陷阱时,target 为源对象也就是 parent ,key 为 value 。

  • 陷阱中返回 Reflect.get(target,key) 相当于 target[key]

  • 此时,不知不觉中 this 指向在 get 陷阱中被偷偷修改掉了!!

  • 原本调用方的 obj 在陷阱中被修改成为了对应的 target 也就是 parent 。

  • 自然而然打印出了对应的 parent[value] 也就是 19Qingfeng 。

这显然不是我们期望的结果,当我访问 obj.value 时,我希望应该正确输出对应的自身上的 name 属性也就是所谓的 obj.value => wang.haoyu 。

那么,Relfect 中 get 陷阱的 receiver 就大显神通了。

const parent = {
  name: '19Qingfeng',
  get value() {
    return this.name;
  },
};

const handler = {
  get(target, key, receiver) {
-   return Reflect.get(target, key);
+   return Reflect.get(target, key, receiver);
  },
};

const proxy = new Proxy(parent, handler);

const obj = {
  name: 'wang.haoyu',
};

// 设置obj继承与parent的代理对象proxy
Object.setPrototypeOf(obj, proxy);

// log: wang.haoyu
console.log(obj.value);

上述代码原理其实非常简单:

  • 首先,之前我们提到过在 Proxy 中 get 陷阱的 receiver 不仅仅会表示代理对象本身同时也还有可能表示继承于代理对象的对象,具体需要区别与调用方。这里显然它是指向继承与代理对象的 obj 。

  • 其次,我们在 Reflect 中 get 陷阱中第三个参数传递了 Proxy 中的 receiver 也就是 obj 作为形参,它会修改调用时的 this 指向。

你可以简单的将 Reflect.get(target, key, receiver) 理解成为 target[key].call(receiver),不过这是一段伪代码,但是这样你可能更好理解。

相信看到这里你已经明白 Relfect 中的 receiver 代表的含义是什么了,没错它正是可以修改属性访问中的 this 指向为传入的 receiver 对象。

总结

相信看到这里大家都已经明白了,为什么Proxy一定要配合Reflect使用。恰恰是为什么触发代理对象的劫持时保证正确的 this 上下文指向。

我们再来稍稍回忆一下,针对于 get 陷阱(当然 set 其他之类涉及到 receiver 的陷阱同理):

  • Proxy 中接受的 Receiver 形参表示代理对象本身或者继承与代理对象的对象。

  • Reflect 中传递的 Receiver 实参表示修改执行原始操作时的 this 指向。

结尾

这里就到了文章的结尾了,至于为什么会突然提到 Proxy & Reflect 的话题。

其实是笔者最近在阅读 Vue/corejs 的源代码内容,刚好它内部大量应用于 Proxy & Reflect 所以就产生了这篇文章。

关于 Proxy 为什么一定要配合 Reflect 使用,具体结合 VueJs 中响应式模块的依赖收集其实会更好理解一些。不过这里为了照顾不太熟悉 VueJs 的同学所以就没有展开了。

当然,最近我也在阅读 VueJs 的过程中尝试书写一些阶段性总结文章。之后在文章中也会详细讲解这一过程,有兴趣的同学可以持续关注我的最新动态~

到此这篇关于js中为什么Proxy一定要配合Reflect使用的文章就介绍到这了,更多相关js Proxy Reflect内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: js中为什么Proxy一定要配合Reflect使用

本文链接: https://www.lsjlt.com/news/144697.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • js中为什么Proxy一定要配合Reflect使用
    目录引言前置知识单独使用 ProxyProxy 中的 receiverReflect 中的 receiver总结结尾引言 EcmaScript 2015 中引入了 Proxy 代理 ...
    99+
    2024-04-02
  • js中Proxy一定要配合Reflect使用的原因是什么
    这篇文章主要介绍“js中Proxy一定要配合Reflect使用的原因是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“js中Proxy一定要配合Reflect使用的原因是什么”文章能帮助大家解决问...
    99+
    2023-06-29
  • vue3中的Proxy一定要用Reflect的原因解析
    目录基本操作疑问总结怎么一直拿到符合期望的值,代理了像没有代理一样用过vue的知道,vue的响应实现用的Proxy,且里面是配合Reflect用的,查看Proxy和Reflect文档...
    99+
    2023-05-17
    vue3中Proxy使用Reflect Proxy使用Reflect
  • web开发中为什么一定要闭合HTML标签
    这篇文章给大家分享的是有关web开发中为什么一定要闭合HTML标签的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一定要闭合HTML标签在以往的页面源代码里,经常看到这样的语句:<li>Some&nbs...
    99+
    2023-06-27
  • 为什么mybatis中的SqlSession一定要关闭
    目录mybatis的SqlSession一定要关闭快的情况慢的情况mybatis中SqlSession使用事项一.SqlSession的使用范围二.SqlSessionFactory...
    99+
    2024-04-02
  • 为什么vue要和nodejs一起使用
    前端开发中,由于业务逻辑越来越复杂,单纯的app就更难以胜任,为了提高开发效率和质量,后端也要为前端提供更多灵活的数据支持,这就涉及到跨域和服务器的简洁性问题。由于前端JavaScript语言的快速发展,人们诞生了新一代的前端服务器端技术,...
    99+
    2023-05-14
  • Java创建线程池为什么一定要用ThreadPoolExecutor
    目录先说结论OOM风险演示内存溢出原因分析使用ThreadPoolExecutor来改进其他创建线程池的问题总结前言: 在 Java 语言中,并发编程都是依靠线程池完成的,而线程池的...
    99+
    2024-04-02
  • MySQL中为什么要使用索引
    小编给大家分享一下MySQL中为什么要使用索引,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!索引是什么?MySQL 官方对索引的...
    99+
    2024-04-02
  • InnoDB表为什么一定要用自增列做主键
    这篇文章主要介绍InnoDB表为什么一定要用自增列做主键,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完! 0、导读 我们先了解下InnoDB引擎表的一些关键特征: InnoD...
    99+
    2024-04-02
  • Java中 equals 重写时为什么一定也要重写 hashCode
    目录1.equals 方法2.hashCode 方法2.1 hashCode 使用3.为什么要一起重写?3.1 Set 正常使用3.2 Set 集合的“异常”...
    99+
    2024-04-02
  • 你知道C++中new和delete为什么要匹配使用吗
    目录前言new 和 delete 做了什么内置类型new 和 new []delete 和 delete [ ]自定义类型new 和 new []delete 和 delete [ ...
    99+
    2023-01-10
    C++ new delete匹配使用 C++ new delete使用 C++ new delete
  • 为什么要在Ruby APP中使用Neo4j
    为什么要在Ruby APP中使用Neo4j,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。每天,我都需要储存大量的数据,而我可以使用很多工具,比如 Postgre...
    99+
    2023-06-17
  • 为什么有些人一定要租用日本服务器
    租用日本服务器的优势有:1、日本服务器有国际带宽充足的优势;2、日本服务器有无需备案的优势;3、日本服务器有访问速度快、稳定性强的优势;4、日本服务器有对网站限制少的优势;5、日本服务器有网络品质好的优势。具体内容如下:日本服务器的国际带宽...
    99+
    2024-04-02
  • 剖析Java中在Collection集合中使用contains和remove为什么要重写equals
    目录引言源码剖析实例测试String类和包装类的特殊情况自定义类型总结引言 在Collection集合中: contains方法是判断一个集合里面是否包含指定元素,如果有则返回tru...
    99+
    2024-04-02
  • 为什么PHP中需要使用转义符?
    为什么PHP中需要使用转义符? PHP是一种广泛应用于Web开发的服务器端脚本语言,它在处理字符串时经常需要使用转义符。转义符在PHP中起到了保护特殊字符的作用,防止这些字符被误解或者...
    99+
    2024-04-02
  • 为什么要停止在JavaScript中使用类
    为什么要停止在JavaScript中使用类,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。多年来,OOP(面向对象编程)是软件工程中的事实上的标...
    99+
    2024-04-02
  • 为什么PHP中需要使用转义符?
    为什么PHP中需要使用转义符? PHP是一种广泛应用于Web开发的服务器端脚本语言,它在处理字符串时经常需要使用转义符。转义符在PHP中起到了保护特殊字符的作用,防止这些字符被误解或者...
    99+
    2024-04-02
  • C++中为什么要使用动态内存
    为什么要使用动态内存 1.按需分配,根据需要分配内存,不浪费 int main(void) { int money[10] = { 1, 2, 3 ,4, 5, 6, 7, 8, ...
    99+
    2024-04-02
  • JVM:你知道为什么对象一定在堆中分配吗
    目录JVM运行时数据区域:逃逸分析技术存在的问题:逃逸分析JVM参数设置:总结JVM运行时数据区域: JVM运行时的数据区域可以分为:虚拟机栈、本地方法栈、方法区、程序计数器、堆。一...
    99+
    2024-04-02
  • 为什么使用 Java 接口重定向时要考虑 JavaScript?
    Java 接口重定向是一个非常常见的技术,但是在使用的时候,我们需要考虑 JavaScript 的影响。本文将介绍为什么使用 Java 接口重定向时要考虑 JavaScript,并提供一些演示代码。 首先,让我们简单介绍一下 Java 接口...
    99+
    2023-07-10
    接口 重定向 javascript
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作