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 行为,如果属性不存在,返回 undefined
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) { 
     // 使用 Reflect.set() 调用默认行为,成功返回true
     return 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表单等。
  • 以及扩展对象功能和方法劫持,添加自定义功能或修改现有功能。
  • 还有很多。。。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

线上3D博物馆搭建简单吗?有何优势?有哪些应用场景?

随着科技的飞速发展,传统的博物馆参观方式正在经历一场前所未有的变革,在科技的“加持”下,不少博物馆凭借强大的技术、创意和美学实践,频频“出圈”,线上3D博物馆逐渐崛起,这不仅丰富了人们的文化体验&…

Java集合之HashMap

概述 HashMap是基于哈希表的Map接口实现的一种存储key、value的数据结构,提供了所有可选的映射操作,且键值允许null的存在,不保证数据映射的顺序,也不能保证顺序在一段时间内保持不变 底层结构 jdk1.7:数组链表 jdk…

污水处理环保设备厂商怎么选

在选择污水处理环保设备厂商时,需要综合考虑多个因素来确保选取的供应商能够提供高质量的设备和服务。以下是一些主要的考虑因素: 企业资质和认证:首先检查供应商是否拥有相关的资质证书和行业认证,例如ISO 9001质量管理体系认证、…

[Linux]一篇文章带你全面理解信号

文章目录 初识信号一、什么是信号二、为什么要有信号 看见信号一、先见一下Linux中的信号:二、如何产生信号三、自定义信号的处理行为(自定义捕捉) 了解信号一、信号的保存二、block、pending表使用代码查看三、一些倔强的,无法被…

亚马逊自养号测评策略:提升店铺产品权重的秘诀

对于卖家而言,拥有一款爆款产品无疑是获得流量的关键,同时它也能显著提升店铺的销量。因此,大部分卖家都热衷于学习如何打造爆款产品的策略,特别是对于那些致力于经营自己店铺的卖家来说,掌握这一技巧对于店铺的成功运…

JWT令牌技术实现登录校验

一.简单登录功能 在登录界面中,我们可以输入用户的用户名以及密码,然后点击 "登录" 按钮就要请求服务器,服务端判断用户输入的用户名或者密码是否正确。如果正确,则返回成功结果,跳转至系统首页面。 1.功能…

前端JS必用工具【js-tool-big-box】学习,生成uuid,数组去重

js-tool-big-box这个前端工具库,今天又添加了2个实用功能,分别是生成uuid和数组去重。 目录 1 安装并引入 2 生成uuid 3 数组去重 1 安装并引入 安装最新版的js-tool-big-box工具包 由于生成uuid和数组去重属于两个不同对象下的方法,所以…

照片尺寸怎么修改?这几个图片处理方式都可以

修改图片尺寸在许多场景中都是常见的需求,包括网页设计、图片编辑、手机应用程序开发、幻灯片演示、社交媒体和博客、以及打印和出版物设计,通过调整图片大小,可以适应不同的布局和设备要求,那么问题来了,如何将图片改…

国外客户怀疑我们产品质量要如何应对

经常有外贸小伙伴问我,国外客户怀疑我们的产品质量要如何应对? 这个问题应该算是外贸经常遇到的一个问题,今天就简单来给大家分享几个我认为可以去入手跟客户回复解决的这个问题的点。 首先,我们要知道,不管你做啥产品…

DBeaver配置离线驱动

因为部署的服务器为无网环境,所以在服务器上使用DBeaver需要配置离线驱动 我们在有网的环境下,安装DBeaver。把驱动下载下来,然后再拷贝到没网的设备上 一、下载驱动 1.在有网的设备上,打开DBeaver 2.找到窗口,选择…

【Linux】用户组、用户、文件权限(ugo权限),权限掩码,chmod,chown,suid,sgid,sticky,su,sudo

用户组 注意:普通用户只能查看有哪些组,不能创建/修改/删除,会提示:用户名 is not in the sudoers file.This incident will be reported. groupadd 用户组名新建用户组cat /etc/group查看有哪些组(普通用户可以操作…

云原生 初识Kubernetes的理论基础

一、k8s 的由来及其技术运用 1.1 k8s的简介 Kubernetes,词根源于希腊语的 舵手、飞行员。在国内又称k8s(因为k和s之间有8个字母,所以得名。“国内程序员的幽默”)。 作用: 用于自动部署、扩展和管理“容器化&#x…

【JAVA进阶篇教学】第十六篇:Java中AOP使用

博主打算从0-1讲解下java进阶篇教学,今天教学第十五篇:Java中AOP使用。 AOP(Aspect-Oriented Programming)是一种编程范式,它允许开发者在不修改源代码的情况下,对代码进行横切关注点的分离和增强。在 Java…

基于区块链的Web 3.0关键技术研讨会顺利召开

基于区块链的Web3.0关键技术研讨会 2024年4月23日,由国家区块链技术创新中心主办的“基于区块链的web3.0关键技术研讨会”召开。Web3.0被用来描述一个运行在“区块链”技术之上的“去中心化”的互联网,该网络上的主体掌握自己数据所有权和使用权&#xf…

python与anaconda 的对应关系

不能下载好anaconda 后才能知道python吧 python10。2023年3月 python11 2023年7月 具体请看官方说明 Anaconda 2023.09-0 — Anaconda documentation 示例如下,绿色框,有的在包的列表中搜python就可以找到

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…

idea连接远程仓库

git ->克隆。 url为远程仓库的地址&#xff0c;输入好后&#xff0c;选择项目存放目录&#xff0c;再点击克隆 点击新窗口打开。 切换到对应分支

使用 Gin-Docs 自动生成 API 文档

该插件移植自 Python 的 Flask-Docs&#xff0c;可以根据代码注释生成文档页面&#xff0c;支持离线文档下载和生成&#xff0c;支持在线调试&#xff0c;支持密码认证。 Gin-Docs Gin API 文档自动生成插件 特性 根据代码注释自动生成 Markdown 文档支持离线 Markdown 文档下…