在js中使用proxy的棘手问题

在js中使用proxy的棘手问题

ES2015引入了大量的新功能,其中一个特性是Proxy(查看proxy详细介绍与使用)。虽然proxy能代来非常多好处,但是它具有一些限制。有人会称之为"设计缺陷"。在这篇文章里,我们就来看看一些棘手的问题。

proxy实例

让我创建一个简单的proxy实例,了解平台如何工作的最简单方法是从记录与底层目标的交互引用开始。对于我们的例子,我们将使用一个简单的实例 Person 作为我们的代理目标。

// person.js
export class Person {constructor(firstName, lastName) {this.firstName = firstName;this.lastName = lastName;}get fullName() {return `${this.firstName} ${this.lastName}`;}introduceYourselfTo(other = "friend") {console.log(`Hello ${other}! My name is ${this.fullName}.`);}
}

让我们创建一个基本的 proxy 来拦截所有的属性访问并将其打印到控制台。

// person-proxy.js
import { Person } from "./person.js";const leo = new Person("leo", "lau");const proxy = new Proxy(leo, {get(target, property) {console.log(`Access: "${property}"`);return Reflect.get(target, property);},
});proxy.introduceYourselfTo("jack");

上面的代码实例化了一个 Proxy 对象,传递了一个 Person 对象来作为要代理的对象,同时设置了一些 ”陷阱“ 配置。

这些"陷阱"是与运行时挂钩,可以让我们拦截与目标的交互。在上面的例子中,我们的 get 方法有两件事要做:

  1. 首先,将正在检索的对象键进行记录。
  2. 因为我们仍然希望对象正常工作,所以我们使用 Reflect API从目标的"内部槽"中获取属性值,然后从“陷阱”中返回。

所有对象都将数据存储在内部插槽中,这些插槽无法直接从代码中访问。…Reflect API提供了一种方法来调用能够与对象的内部槽进行交互的内部运行时方法。

上面的打印为:

Access: "introduceYourselfTo"
Access: "fullName"
Hello jack! My name is leo lau.

在使用 proxy 时,重要的是要记住javascript对象是如何工作的细节。当调用方法时,必须首先调用对象上的 get 方法。这就是为什么我们看到第一个日志语句显示 Access: "introduceYourselfTo"。然后,当该方法应用于 proxy 时,运行时将调用get方法获取fullName

但是为什么没有打印出 firstNamelastName 呢,毕竟我们在访问 fullName的时候内部是访问了firstNamelastName 的。

要理解这一点,就需要深入了解在 javascript 运行时发生的事情。

在上面的代码中,introduceYourselfTo 方法是通过在 Proxyget 方法中检索的,调用 proxy.introduceYourselfTo("jack") 方法,此时上下文 this 指向 proxy 对象,运行时通过 proxy 对象获取到 fullName,此时就再一次触发 proxy中的 get 方法并打印 Access: "fullName"。这里就是它变得有趣的地方。

当我们使用 Reflect.get(target, property) 运行时将访问内部的 fullName。因为fullName 是一个属性,它会调用在属性描述符上设置的get方法。此时 fullName中的 this 是属于 target 而不是 proxy。所以我们在proxy中设置的拦截方法无法拦截 firstNamelastName

所以,如果我们想拦截所有的东西怎么解决?我们的第一个想法可能是把 proxy 对象本身传递给 Reflect.get

const proxy = new Proxy(john, {get(target, property) {console.log(`Access: "${property}"`);return Reflect.get(proxy, property);}
});

千万不能这么做,这将导致无限循环

Reflect 将试图通过 proxy 获得属性值,而proxy将再次为相同的属性调用设置的拦截方法,它又将试图通过 proxy 获得属性值。

我们需要的是一种方法来告诉 Reflect 哪个对象可以访问内部插槽。但是,在它从内部插槽检索到属性之后,我们希望用proxy来运行属性的getter方法。

为此,我们需要设置Reflect的第三个参数 receiver

// person-proxy-with-receiver.js
import { Person } from "./person.js";const leo = new Person("leo", "lau");const proxy = new Proxy(leo, {get(target, property, receiver) {console.log(`Access: "${property}"`);return Reflect.get(target, property, receiver);}
});proxy.introduceYourselfTo("jack");

通过这个代码,我们可以看到以下输出:

Access: "introduceYourselfTo"
Access: "fullName"
Access: "firstName"
Access: "lastName"
Hello jack! My name is leo lau.

前面只提到了一个get方法的使用,proxy还可以设置其他非常多的方法,详情可以查看这篇文章。

proxy数据保护

通过Proxy.revocable(...)这个方法可以创建一个可撤销代理的数据。这种类型的代理可以被代理的创建者禁用,这样所有仍然持有引用的对象都将被运行时阻止访问对象。这里是一个可撤销的实例:

const leo = new Person("leo", "lau");const { proxy, revoke } = Proxy.revocable(leo, {get(target, property, receiver) {console.log(`Access: "${property}"`);return Reflect.get(target, property, receiver);}
});proxy.introduceYourselfTo("jack");
revoke();
proxy.introduceYourselfTo("Bad Guy");

执行上面的方法会输出如下内容:

Access: "introduceYourselfTo" 
Access: "fullName" 
Access: "firstName" 
Access: "lastName" 
Hello jack! My name is leo lau.
Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked

Proxy.revocable会返回一个revoke方法, 如果把这个方法暴露出去,就可以通过调用revoke方法来访问撤销代理。

proxy 中遇到的问题

不能安全地在具有私有成员的对象上使用代理

改写之前的例子:

class Person {#firstName;#lastName;constructor(firstName, lastName) {this.#firstName = firstName;this.#lastName = lastName;}get firstName() {return this.#firstName;}get lastName() {return this.#lastName;}get fullName() {return `${this.firstName} ${this.lastName}`;}introduceYourselfTo(other = "friend") {console.log(`Hello ${other}! My name is ${this.fullName}.`)}
}

现在我们在proxy中使用:

const leo = new Person("leo", "lau");const proxy = new Proxy(leo, {get(target, property) {console.log(`Access: "${property}"`);return Reflect.get(target, property);}
});proxy.introduceYourselfTo("jack");

输出为:

Access: "introduceYourselfTo" 
Access: "fullName" 
Hello jack! My name is leo lau.

看起来没啥问题,但是如果我们使用了receiver

const leo = new Person("leo", "lau");const proxy = new Proxy(leo, {get(target, property, receiver) {console.log(`Access: "${property}"`);return Reflect.get(target, property, receiver);}
});proxy.introduceYourselfTo("jack");

输出:

Access: "introduceYourselfTo" 
Access: "fullName" 
Access: "firstName" 
Uncaught TypeError: Cannot read private member #firstName 
from an object whose class did not declare it

当我们使用receiver时,firstName中的this指向proxy,这个getter指向一个私有属性,不能通过this获取,因此会出现一个运行时的错误。

对于proxy来说这是一个很大的问题,因为我们不能随意控制和验证实现的对象(任何对象都可以使用私有成员,并根据proxy是如何写入的,与对象的特定内部文件相结合,但是使用proxy去调用的话就会导致错误)

由于这些原因,在使用代理或将对象传递给使用代理的其他库时,我们需要非常小心。

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

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

相关文章

MyCat实现分库分表技术

目录 一、分库分表 1.1介绍 1.1.1问题分析 1.1.2拆分策略 1.1.3垂直拆分 1.1.3.1垂直分库 1.1.3.2垂直分表 1.1.4水平拆分 1.1.4.1水平分库 1.1.4.2水平分表 1.1.5实现技术 二、MyCat概述 2.1介绍 2.2下载 2.3安装 2.4目录介绍 2.5概念介绍 三、MyCat入门 3.…

MyBatisPlus(八)范围查询

说明 范围查询&#xff0c;包括&#xff1a; 大于大于等于小于小于等于在范围内在范围外 大于&#xff1a;gt 代码 Testvoid gt() {LambdaQueryWrapper<User> wrapper new LambdaQueryWrapper<>();wrapper.gt(User::getAge, 20);List<User> users mapp…

MyBatis-plus快速代码生成,使用MybatisX插件

概述 MyBatis提供逆向工程&#xff0c;可以快速生成代码。 而MyBatis-plus也提供了一个代码生成器&#xff0c;它需要执行一些代码来实现。 而MybatisX 是一款基于 IDEA 的快速开发插件&#xff0c;为效率而生&#xff0c;你不需要写代码&#xff0c;直接界面化操作&#xf…

python 学习随笔 4

列表list 将序列前几个进行替换&#xff08;数量可以不同&#xff09; 将序列进行间隔替换&#xff08;必须保证数量相同&#xff0c;否则报错&#xff09; 删除序列内元素 向序列后新增一个元素 向序列后新增多个元素 将序列进行数乘&#xff08;不是产生几个序列哦&#xff0…

三、git的安装和配置

一、安装 1.官网下载&#xff1a;https://git-scm.com/download 下载最新版本&#xff0c;点击红框或篮筐处即可 2.点击下载好的安装包安装这个软件 3.一直点击next&#xff0c;直到出现install&#xff0c;点击install&#xff0c;安装完成后点击finish&#xff1a; 下载完成…

【Mysql专题】视图介绍及其基本操作

前言 前段时间&#xff0c;跟客户开个线上会议&#xff0c;在和对方技术人员讨论到如何把数据给我们的时候&#xff0c;对方说【丢给你们一个视图&#xff0c;你们查视图就好了】我一下子懵了。当时现场贼尴尬&#xff0c;我只能假装断线了&#xff0c;然后又模棱两可的说了几…

【网络安全---ICMP报文分析】Wireshark教程----Wireshark 分析ICMP报文数据试验

一&#xff0c;试验环境搭建 1-1 试验环境示例图 1-2 环境准备 两台kali主机&#xff08;虚拟机&#xff09; kali2022 192.168.220.129/24 kali2022 192.168.220.3/27 1-2-1 网关配置&#xff1a; 编辑-------- 虚拟网路编辑器 更改设置进来以后 &#xff0c;先选择N…

10.1 调试事件读取寄存器

当读者需要获取到特定进程内的寄存器信息时&#xff0c;则需要在上述代码中进行完善&#xff0c;首先需要编写CREATE_PROCESS_DEBUG_EVENT事件&#xff0c;程序被首次加载进入内存时会被触发此事件&#xff0c;在该事件内首先我们通过lpStartAddress属性获取到当前程序的入口地…

阿里云免费服务器无法领取限制说明

阿里云提供免费服务器供用户申请&#xff0c;但是领取免费服务器是有条件的&#xff0c;并不是有所的阿里云用户均可领取免费云服务器&#xff0c;免费服务器领取条件为&#xff1a;账号从未使用过阿里云服务器的用户&#xff0c;阿里云百科来举例说明免费服务器领取说明&#…

STM32复习笔记(六):STM32远程升级BootLoader相关

目录 Preface&#xff1a; &#xff08;一&#xff09;STM32上电启动流程 &#xff08;二&#xff09;BootLoader相关 &#xff08;三&#xff09;Clion配置 Preface&#xff1a; 有关STM32的BootLoader主要还是参考了许多大佬的文章&#xff0c;这里只是简单地列举一下&am…

基于SpringBoot的体育馆场地赛事预约管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

【C++】基础入门

万字复习C基础入门语法&#xff0c;适合学过C的朋友用来复习查阅&#xff0c;可能不太适合0基础的朋友。 一.c初识 (1) 第一个c程序 最简单的格式&#xff1a; // 导入头文件 #include<iostream> // 简化对命名空间std下函数和对象的使用 using namespace std; // …

Java基于SSM的校园一卡通系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

go语法入门2

字符串 使用双引号或反引号引起来的任意个字符。它是字面常量。 func main() {var a "abc\n测试" // \n换行fmt.Println(a) } abc 测试func main() {var a "abc\n\t测试" \\换行后在tabfmt.Println(a) } abc测试func main() {var a abc测试 …

好奇喵 | Surface Web ---> Deep Web ---> Dark Web

前言 我们可能听说过深网(deep Web)、暗网(dark Web)等名词&#xff0c;有些时候可能会认为它们是一个东西&#xff0c;其实不然&#xff0c;两者的区别还是比较大的。 什么是deep web&#xff1f; 深网是网络的一部分&#xff0c;与之相对应的是表层网络&#xff08;surface …

SketchUp Pro 2023 for Mac——打造你的创意之城

SketchUp Pro 2023 for Mac是一款专业级的3D建模软件&#xff0c;为你提供最佳的设计和创意工具。不论你是建筑师、室内设计师&#xff0c;还是爱好者&#xff0c;SketchUp Pro都能满足你对于创意表达的需求。 SketchUp Pro 2023拥有强大而直观的界面&#xff0c;让你轻松绘制…

C++入门-day01

一、认识C C融合了三种不同的编程方式 C代表的过程性语言在C基础上添加的类、结构体puls代表的面向对象语言C模板支持泛型编程 C完全兼容C的特性 Tips&#xff1a;侯捷老师提倡的Modren C是指C11、C14、C17和C20这些新标准所引入的一系列新特性和改进。在我们练习的时候也应当去…

PG 多表连接查询

写法&#xff1a; 使用 select 表名.键名 from 表1 join表2 on 相同的主键 构造出来一张新表 多表要用表名.键名 才能知道是哪一张表 传统写法也行 类型&#xff1a; 内 而外的要这样写

复习C语言数组的用法

实验内容 1.1设计一个函数fun&#xff0c;功能是有N*N的矩阵&#xff0c;根据给定的m值&#xff0c;m<N,将每行元素中的值&#xff0c;均往右移m个位置&#xff0c;左边置0 #include<stdio.h> void fun(int (*a)[3],int m){int n,j,i,k,num;int p2;//右移位置列数nu…

Debezium日常分享系列之:使用数据库中的数据流进行在线机器学习

Debezium日常分享系列之&#xff1a;使用数据库中的数据流进行在线机器学习 一、背景介绍二、数据集准备三、使用 Apache Flink 进行分类四、使用 Debezium 和 Kafka 作为源数据流五、构建 Flink 流 k-means六、评估模型七、使用 Apache Spark 进行分类八、定义数据流九、定义和…