在js中使用proxy的棘手问题

2023-10-05 01:54
文章标签 问题 使用 js proxy 棘手

本文主要是介绍在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去调用的话就会导致错误)

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

这篇关于在js中使用proxy的棘手问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/3140

相关文章

SpringBoot中使用Flux实现流式返回的方法小结

《SpringBoot中使用Flux实现流式返回的方法小结》文章介绍流式返回(StreamingResponse)在SpringBoot中通过Flux实现,优势包括提升用户体验、降低内存消耗、支持长连... 目录背景流式返回的核心概念与优势1. 提升用户体验2. 降低内存消耗3. 支持长连接与实时通信在Sp

python使用库爬取m3u8文件的示例

《python使用库爬取m3u8文件的示例》本文主要介绍了python使用库爬取m3u8文件的示例,可以使用requests、m3u8、ffmpeg等库,实现获取、解析、下载视频片段并合并等步骤,具有... 目录一、准备工作二、获取m3u8文件内容三、解析m3u8文件四、下载视频片段五、合并视频片段六、错误

gitlab安装及邮箱配置和常用使用方式

《gitlab安装及邮箱配置和常用使用方式》:本文主要介绍gitlab安装及邮箱配置和常用使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1.安装GitLab2.配置GitLab邮件服务3.GitLab的账号注册邮箱验证及其分组4.gitlab分支和标签的

SpringBoot3应用中集成和使用Spring Retry的实践记录

《SpringBoot3应用中集成和使用SpringRetry的实践记录》SpringRetry为SpringBoot3提供重试机制,支持注解和编程式两种方式,可配置重试策略与监听器,适用于临时性故... 目录1. 简介2. 环境准备3. 使用方式3.1 注解方式 基础使用自定义重试策略失败恢复机制注意事项

nginx启动命令和默认配置文件的使用

《nginx启动命令和默认配置文件的使用》:本文主要介绍nginx启动命令和默认配置文件的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录常见命令nginx.conf配置文件location匹配规则图片服务器总结常见命令# 默认配置文件启动./nginx

在Windows上使用qemu安装ubuntu24.04服务器的详细指南

《在Windows上使用qemu安装ubuntu24.04服务器的详细指南》本文介绍了在Windows上使用QEMU安装Ubuntu24.04的全流程:安装QEMU、准备ISO镜像、创建虚拟磁盘、配置... 目录1. 安装QEMU环境2. 准备Ubuntu 24.04镜像3. 启动QEMU安装Ubuntu4

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ

Python常用命令提示符使用方法详解

《Python常用命令提示符使用方法详解》在学习python的过程中,我们需要用到命令提示符(CMD)进行环境的配置,:本文主要介绍Python常用命令提示符使用方法的相关资料,文中通过代码介绍的... 目录一、python环境基础命令【Windows】1、检查Python是否安装2、 查看Python的安

Python并行处理实战之如何使用ProcessPoolExecutor加速计算

《Python并行处理实战之如何使用ProcessPoolExecutor加速计算》Python提供了多种并行处理的方式,其中concurrent.futures模块的ProcessPoolExecu... 目录简介完整代码示例代码解释1. 导入必要的模块2. 定义处理函数3. 主函数4. 生成数字列表5.