在js中使用proxy的棘手问题

2023-10-04 22:15
文章标签 问题 使用 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/2833

相关文章

使用Redis快速实现共享Session登录的详细步骤

《使用Redis快速实现共享Session登录的详细步骤》在Web开发中,Session通常用于存储用户的会话信息,允许用户在多个页面之间保持登录状态,Redis是一个开源的高性能键值数据库,广泛用于... 目录前言实现原理:步骤:使用Redis实现共享Session登录1. 引入Redis依赖2. 配置R

使用Python的requests库调用API接口的详细步骤

《使用Python的requests库调用API接口的详细步骤》使用Python的requests库调用API接口是开发中最常用的方式之一,它简化了HTTP请求的处理流程,以下是详细步骤和实战示例,涵... 目录一、准备工作:安装 requests 库二、基本调用流程(以 RESTful API 为例)1.

使用Python开发一个Ditto剪贴板数据导出工具

《使用Python开发一个Ditto剪贴板数据导出工具》在日常工作中,我们经常需要处理大量的剪贴板数据,下面将介绍如何使用Python的wxPython库开发一个图形化工具,实现从Ditto数据库中读... 目录前言运行结果项目需求分析技术选型核心功能实现1. Ditto数据库结构分析2. 数据库自动定位3

Python yield与yield from的简单使用方式

《Pythonyield与yieldfrom的简单使用方式》生成器通过yield定义,可在处理I/O时暂停执行并返回部分结果,待其他任务完成后继续,yieldfrom用于将一个生成器的值传递给另一... 目录python yield与yield from的使用代码结构总结Python yield与yield

Go语言使用select监听多个channel的示例详解

《Go语言使用select监听多个channel的示例详解》本文将聚焦Go并发中的一个强力工具,select,这篇文章将通过实际案例学习如何优雅地监听多个Channel,实现多任务处理、超时控制和非阻... 目录一、前言:为什么要使用select二、实战目标三、案例代码:监听两个任务结果和超时四、运行示例五

python使用Akshare与Streamlit实现股票估值分析教程(图文代码)

《python使用Akshare与Streamlit实现股票估值分析教程(图文代码)》入职测试中的一道题,要求:从Akshare下载某一个股票近十年的财务报表包括,资产负债表,利润表,现金流量表,保存... 目录一、前言二、核心知识点梳理1、Akshare数据获取2、Pandas数据处理3、Matplotl

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3