Proxy和Reflect,打造灵活的JS代理机制 (代码示例)

在 JavaScript 中,代理(Proxy)和反射(Reflect)是 ES6 引入的两个新特性。Proxy用于创建一个对象的代理,从而实现对这个对象的操作的拦截、转换或扩展;而Reflect则提供了一系列与 JavaScript 运行时进行交互的方法,使得开发者可以更方便地操作 JavaScript 对象。

我对这篇文章进行了精炼处理,力求言简意赅。希望对你有所帮助,有所借鉴~~

JavaScript 中的代理与拦截

Proxy 对象用于创建一个对象的代理,从而可以在访问原始对象之前先介入并操作某些操作(如属性查找、赋值、枚举、函数调用等)。代理可以控制对内部对象的访问,并可以按照需要自定义行为。
Proxy的语法如下:

const proxy = new Proxy(target, handler);
  • target:目标对象,即被代理的对象。
  • handler:处理程序对象,定义了代理对象的方法,用于拦截和定义目标对象的操作。

我们看一个实例代码:

const target = {name: 'Alice',age: 25
};const handler = {get(target, prop, receiver) {console.log(`访问了属性:${prop}`);return target[prop];},set(target, prop, value, receiver) {console.log(`设置了属性:${prop},值为:${value}`);target[prop] = value;return true;}
};const proxy = new Proxy(target, handler);console.log(proxy.name); // 输出:访问了属性:name,Alice
proxy.age = 30; // 输出:设置了属性:age,值为:30
console.log(proxy.age); // 输出:访问了属性:age,30

通过创建了一个 Proxy,拦截了target属性的访问和赋值操作,并在这些操作发生时打印出相应的信息。

JavaScript 中的反射

Reflect 是一个内置的对象,它提供了拦截 JavaScript 操作的方法。这些方法与 Proxy 处理器对象的方法相对应。但是又有所不同,我们接着往下看。

// 定义目标对象
const target = {name: 'Alice',age: 25
};// 使用 Reflect.get() 来获取属性值
const name = Reflect.get(target, 'name');
console.log(name); // 输出:Alice// 使用 Reflect.set() 来设置属性值
Reflect.set(target, 'age', 30);
console.log(target.age); // 输出:30// 使用 Reflect.has() 来检查属性是否存在
const hasAge = Reflect.has(target, 'age');
console.log(hasAge); // 输出:true// 使用 Reflect.deleteProperty() 来删除属性
Reflect.deleteProperty(target, 'name');
console.log(target.name); // 输出:undefined// 使用 Reflect.ownKeys() 来获取对象的所有自有属性的键
const keys = Reflect.ownKeys(target);
console.log(keys); // 输出:['age']

Reflect 的方法与 JS 语言内部的操作紧密对应,使得在编写代理处理程序时能够轻松地调用原始操作。

那么为什么还需要 Reflect 呢?🧐🧐🧐

Proxy 的局限性

JavaScript 中的 Proxy 提供了一种强大且灵活的方式来拦截并定义对象的基本操作的自定义行为。然而,单独使用 Proxy 在某些情况下可能会遇到一些局限性,特别是在尝试模仿默认行为时。

例如,如果我们想要在拦截属性的读取操作时,仍然返回属性的默认值,我们就需要在处理程序中实现这一点。

const target = {name: 'Alice',age: 25
};const handler = {get(target, prop, receiver) {if (prop in target) {return target[prop]; // 手动模仿默认的 get 行为}return undefined; // 如果属性不存在,返回 undefined},set(target, prop, value, receiver) { if (prop === 'age' && typeof value !== 'number') { throw new TypeError('Age must be a number'); } // 手动实现默认行为 target[prop] = value; return true; }
};const proxy = new Proxy(target, handler);
console.log(proxy.name); // 输出:Alice

这种方式虽然可行,但不够优雅,因为它要求开发者手动实现语言的默认行为,并且容易出错。

而这时,Reflect 的作用就凸显出来了。Reflect 提供了一组与 Proxy 一一对应的静态方法,这些方法可以用来调用对象的默认行为。这使得在编写代理处理程序时,可以轻松地模仿或调用默认行为。

const target = {name: 'Alice',age: 25
};const handler = {get(target, prop, receiver) {// 使用 Reflect 模仿默认的 get 行为,如果属性不存在,返回 undefinedreturn Reflect.get(target, prop, receiver);},set(target, prop, value, receiver) { // 使用 Reflect.set() 调用默认行为,成功返回truereturn Reflect.set(target, prop, value, receiver); }
};const proxy = new Proxy(target, handler);
console.log(proxy.name); // 输出:Alice

怎么样,是不是通过使用 Reflect ,让代码编写和维护,变得更简单了。

Reflect 的必要性

以下是使用 Reflect 的一些优势:

  1. 默认行为的一致性Reflect 对象提供了与大多数 Proxy traps 对应的方法,使得在进行对象操作时,可以保持一致的编程模式,且代码的可读性和可维护性很强。比如,使用 Reflect.get()Reflect.set()Reflect.has() 等,而不是直接访问对象属性或使用 in 操作符。
  2. 更好的错误处理Reflect 方法返回一个布尔值,可以清晰地指示操作是否成功,这使得错误处理更加直观。相比之下,传统的操作方法可能会抛出异常,需要通过 try...catch 来处理。
  3. 函数式编程风格Reflect 方法接受目标对象作为第一个参数,这允许你以函数式编程风格处理对象操作,而不是使用命令式编程风格。
  4. 接收者(receiver)参数Reflect 方法通常接受一个接收者参数,这允许你在调用方法时明确指定 this 的值,这在实现基于原型的继承和自定义 this 绑定时非常有用。

这里再详细说下接收者(receiver)参数这一块,不感兴趣的伙伴可以直接跳过这块。下面是一个示例,我们看下如何使用接收者参数来实现一个简单的自定义 this 绑定:

// 定义一个目标对象
const target = {name: 'Alice',age: 25,greet: function() {return `Hello, my name is ${this.name} and I am${this.age} years old.`;}
};// 定义一个代理处理程序
const handler = {get(target, prop, receiver) {if (prop === 'greet') {// 使用 Reflect.get() 来调用目标对象的 greet 方法,并指定接收者return function(...args) {return Reflect.apply(target[prop], receiver, args);};}return Reflect.get(target, prop, receiver);}
};// 创建代理对象
const proxy = new Proxy(target, handler);// 调用代理对象的 greet 方法
console.log(proxy.greet()); // 输出:Hello, my name is Alice and I am 25 years old.

目标对象 target,有一个 greet 函数方法。

  • 首先我们在 handler 中拦截了对 greet 方法的访问。
  • get 捕获器中,我们检查属性名是否为 greet,如果是,我们返回一个新的函数。
  • 这个新函数使用 Reflect.apply() 来调用目标对象的 greet 方法,并指定接收者为代理对象 proxy

这样,当调用 proxy.greet() 时,greet 方法内部的 this 将指向代理对象 proxy,而不是目标对象 target

这使得我们能够在调用方法时自定义 this 的值,实现基于原型的继承和自定义 this 绑定。

Proxy 与 Reflect 的结合

上面我们通过Proxy的局限性和Reflect的必要性,介绍了为什么还需要Reflect,下面我们就进入实战,将这两者结合起来使用。

例如,以下代码使用Reflect实现了对目标对象属性的读取、设置和枚举的拦截:

const target = {name: '张三',age: 25
};
const handler = {get(target, prop, receiver) {return Reflect.get(target, prop, receiver);},set(target, prop, value, receiver) {return Reflect.set(target, prop, value, receiver);},has(target, prop) {return Reflect.has(target, prop);},enumerate(target) {return Reflect.enumerate(target);}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // 输出:张三
proxy.age = 30;
console.log(proxy.age); // 输出:30
console.log(Object.keys(proxy)); // 输出:["name", "age"]

高端的食材,往往只需要简单的烹饪,这样代码简洁!

总结

通过使用 Proxy,我们可以轻松地实现对象的代理和拦截操作。而Reflect 的引入为与 Proxy 的配合提供了统一和规范的方式来操作对象。比如:

  • 我们可以实现“数据绑定和观察者模式”,用来实现数据的双向绑定,通过拦截对象属性的读取和设置操作,可以自动通知变更。
  • 同时,也可以用来“验证和数据校验”,如校验form表单等。
  • 以及扩展对象功能和方法劫持,添加自定义功能或修改现有功能。
  • 还有很多。。。

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

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

相关文章

软件提示找不到msvcr120.dll怎么修复,分享5种靠谱的修复方法

当您在使用电脑过程中遇到程序运行出错,提示缺少msvcr120.dll文件怎么办。msvcr120.dll是Microsoft Visual C Redistributable Package的一部分,主要用于支持某些应用程序运行所需的C库文件。如果该文件丢失或损坏,依赖于此文件的应用程序便无…

Kotlin扩展函数和运算符重载

扩展函数 fun String.lettersCount():Int{var count 0for(i in this){if(i.isLetter())count}return count } fun main(){val str:String "12we"println(str.lettersCount()) } 相当于直接将方法写在类里面。函数体内可以直接使用this而不用传参。 运算符重载 …

IDEA找不到database图标的解决方法

首先右边侧边栏和左边的侧边栏都看一下,确认没有数据库图标以后再参考下面方法。 第一步,打开设置,在插件里搜索database 第二步 安装好,点击确定 返回主页面,左边的侧边栏会出现database图标,点击号就可以…

[更改挂载点]重新挂载硬盘

显示磁盘空间使用情况 df -hdf -h 命令的输出显示了文件系统的磁盘空间使用情况。 这里 /dev/nvme0n1p1 设备(大小为 880GB)已经被挂载到 /media/nvidia/SSD 目录下,并且使用了 304GB,剩余 532GB,使用率为 37%。这意…

Qt自定义QpushButton分别在c++/python中实现

//.h文件#ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QPainter> #include<QMouseEvent> #include<QPropertyAnimation> #include<QResizeEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; }class Widget : public QWi…

JETBRAINS IDES 分享一个2099通用试用码!PhpStorm 2024 版 ,支持一键升级

文章目录 废话不多说上教程&#xff1a;&#xff08;动画教程 图文教程&#xff09;一、动画教程激活 与 升级&#xff08;至最新版本&#xff09; 二、图文教程 &#xff08;推荐&#xff09;Stage 1.下载安装 toolbox-app&#xff08;全家桶管理工具&#xff09;Stage 2 : 下…

Nginx 7层负载均衡的搭建

目录 负载均衡的理解 修改配置文件 测试 1. 选择在 DMZ 区测试&#xff0c;使用 db 服务器进行测试 2.选择在外网测试负载均衡效果 负载均衡的理解 负载均衡&#xff1a;load balancer&#xff0c;简称LB Nginx 既是一个 web 服务器软件&#xff0c;也是一个负载均衡软件&a…

HTML5+CSS3 将图片和文字置于一行

将文字对齐图片中心的水平位置 今天课堂作业上有一段是要做出文字与图片在一行且文字对齐图片的中心位置。课上用inline-block做的&#xff0c;但盒子总是不受控制。于是回来随便找了个图片用vertical-align做成功了。 这是原本的样式&#xff08;加了边框方便看盒子&#xff…

耐克、肯德基、美宝莲…六大品牌的经典广告语是如何诞生的?

近期&#xff0c;创意翻译公司franklyfluent推出了一个名为“Hard to Make, Easy to Break”的创意户外活动&#xff0c;展示了创意和文字艺术在品牌翻译中的重要性。 “Hard to Make, Easy to Break”的活动于2024年5月份在英国正式发布。这些移动广告牌出现在伦敦的各个体育…

OpenAI 重磅发布:ChatGPT Mac 桌面应用震撼上线!

OpenAI 重磅发布&#xff1a;ChatGPT Mac 桌面应用震撼上线&#xff01; 博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff0…

Google IO 2024有哪些看点呢?

有了 24 小时前 OpenAI 用 GPT-4o 带来的炸场之后&#xff0c;今年的 Google I/O 还未开始&#xff0c;似乎就被架在了一个相当尴尬的地位&#xff0c;即使每个人都知道 Google 将发布足够多的新 AI 内容&#xff0c;但有了 GPT-4o 的珠玉在前&#xff0c;即使是 Google 也不得…

2024/5/15 英语每日一段

Many pet owners are now turning to pet insurance policies to avoid higher vet bills should something bad happen unexpectedly. But Carlson said that preventive veterinary care—like vaccination, parasite control and weight management—is "the best way …

【Linux取经路】进程通信之匿名管道

文章目录 一、进程间通信介绍1.1 进程间通信是什么&#xff1f;1.2 进程间通信的目的1.3 进程通信该如何实现 二、管道2.1 匿名管道2.1.1 站在文件描述符角度深入理解管道2.1.2 接口使用2.1.3 PIPE_BUFFER 和 Pipe capacity2.1.4 管道中的四种情况2.1.5 管道特征总结 2.2 匿名管…

Socks5:网络世界的隐形斗篷

在数字化时代&#xff0c;网络隐私和安全已成为人们日益关注的话题。Socks5&#xff0c;作为一种代理协议&#xff0c;为用户在网络世界中的匿名性提供了强有力的支持。本文将从Socks5的多个方面&#xff0c;深入探讨这一技术如何成为网络世界的“隐形斗篷”。 一、Socks5的基本…

【C++小语法技巧】命名空间和输入输出

在使用C语言编程过程中&#xff0c;C语言的要求之严格&#xff0c;编程过程之繁琐&#xff0c;大同小异的重复性工作&#xff0c;令C之父使用C语言编程时也深受其扰&#xff0c;于是乎C兼容C小语法诞生了 一、命名空间域&#xff08;解决C语言中命名冲突&#xff09; 1.定义命…

音视频-H264编码封装- MP4格式转Annex B格式

目录 1&#xff1a;H264语法结构回顾 2&#xff1a;H264编码补充介绍 3&#xff1a;MP4模式转Annex B模式输出到文件示例 1&#xff1a;H264语法结构回顾 在之前文章里介绍过H264的语法结构。 传送门: 视音频-H264 编码NALU语法结构简介 2&#xff1a;H264编码补充介绍 H…

紫光展锐先进技术科普 | 工业互联网遇到5G,1+1>2?

随着工厂自动化的加速普及&#xff0c;如今我们可能经常看到这样的场景&#xff1a;在高温、潮湿、粉尘、腐蚀等恶劣环境作业场景&#xff0c;巡检机器人穿梭其中&#xff0c;工人们不必弯腰去搬沉重又危险的器件&#xff0c;而旁边会有一个个机械臂帮手平稳有序地完成好所有搬…

flutter开发实战-JSON和序列化数据

flutter开发实战-JSON和序列化数据 大多数移动应用都需要与 web 服务器通信&#xff0c;同时在某些时候轻松地存储结构化数据。当创造需要网络连接的应用时&#xff0c;它迟早需要处理一些常见的 JSON。使用Json时候&#xff0c;可以使用json_serializable 一、引入json_anno…

企智汇项目管理软件有哪些优势?

一款非常好用、高效的软件——企智汇软件有哪些优势呢&#xff1f; 首先&#xff0c;我们来看看它的界面设计。企智汇软件界面简洁直观&#xff0c;用户可以轻松地使用各种功能&#xff0c;不需要学习复杂的操作流程。而且&#xff0c;软件还提供了多种配色方案和主题&#xf…

代码随想录Day 47|Leetcode|Python|392.判断子序列 ● 115.不同的子序列

392.判断子序列 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"的…