精读JavaScript中的代理(Proxy)与反射(Reflect)

news2024/10/9 20:25:20

目录

定义与概念

属性及函数

Proxy

Reflect

使用场景

如何实现

实现过程

运行效果

应用限制及优点

写在最后


定义与概念

JavaScript中的Proxy与Reflect是ES6中引入的新特性,它们可以帮助我们更高效地控制对象。

代理(Proxy)是一种设计模式,它允许我们在访问对象的同时,添加一些额外的操作。代理对象与被代理对象实现相同的接口,代理对象会接受并处理所有对被代理对象的访问请求。
代理是对象通过一个代理对象来控制对原对象的读取、设置、调用及其他操作,并对这些操作进行预处理或附加操作,主要用于拦截对象

反射(Reflection)是指程序可以在运行时获取、操作、修改它本身的状态或行为。反射是一种动态获取类型信息和操作类型的能力,可以在运行时动态调用类中的方法或属性。
反射可以使我们知晓(获取)对象详情,操控对象的成员(属性),在调用对象的方法时加入额外的逻辑,主要用于操作对象

在Java中,反射与代理可以通过reflect以及其中的Proxy类与InvocationHandler接口实现代理,通过reflect实现反射;而在C++中则是用继承和虚函数实现代理模式,使用模板和元编程修改检查结构达到反射。代理和反射通常都是编译时的概念,在编译阶段就已经确定了代理和反射的具体实现方式

而JS则是在运行时动态地创建代理和使用反射,并且提供了类似的概念和实现:全局的Proxy与Reflect对象

属性及函数

Proxy

使用new Proxy实例化时需要传入以下两个参数

  • target:被代理的目标对象。
  • handler:定义代理行为的对象。

handler参数中有一些钩子函数,在代理对象发生变化时会触发:

  1. get:在读取代理对象的属性时
  2. set:在对代理对象的属性进行赋值时
  3. has:在使用 in 运算符检查代理对象是否具有某个属性时
  4. deleteProperty:在删除代理对象的属性时
  5. apply:当前代理对象为函数,在调用代理对象时
  6. construct:在使用 new 运算符创建代理对象的实例时
  7. getOwnPropertyDescriptor:在获取代理对象的属性描述符时
  8. defineProperty:在定义代理对象的属性时
  9. getPrototypeOf:在获取代理对象的原型时
  10. setPrototypeOf:在设置代理对象的原型时
  11. isExtensible:在检查代理对象是否可扩展时
  12. preventExtensions:在防止代理对象的扩展时
  13. ownKeys:在返回代理对象的所有键时

Reflect

Reflect对象中的函数与上述handler中的钩子函数是一一对应的

  1. get:用于读取一个对象的属性值
  2. set:用于设置一个对象的属性值
  3. has:用于判断一个对象是否有某个属性
  4. deleteProperty:用于删除一个对象的属性
  5. apply:当前代理对象为函数,用于调用当前对象的方法
  6. construct:用于通过构造函数创建一个新的对象实例
  7. getOwnPropertyDescriptor:用于读取一个对象的自身属性描述对象
  8. defineProperty: 用于为一个对象定义一个属性
  9. getPrototypeOf:用于读取一个对象的原型对象
  10. setPrototypeOf:用于设置一个对象的原型对象
  11. isExtensible:用于判断一个对象是否可扩展
  12. preventExtensions: 用于防止一个对象被扩展
  13. ownKeys:用于读取一个对象的所有自身属性的键名

使用场景

以下代码可以触发所有的钩子函数

const proxyFactory = (target, opts) => {
  return new Proxy(target, {
    set: (target, propertyKey, value, receiver) => {
      // console.log(target, propertyKey, value, receiver);
      console.log("执行了set");
      return Reflect.set(target, propertyKey, value);
    },
    get: (target, property, receiver) => {
      // console.log(target, property, receiver);
      console.log("执行了get");
      return Reflect.get(target, property, receiver);
    },
    has: (target, property) => {
      // console.log(target, property);
      console.log("执行了has");
      return Reflect.has(target, property);
    },
    deleteProperty: (target, property) => {
      // console.log(target, property);
      console.log("执行了deleteProperty");
      return Reflect.deleteProperty(target, property);
    },
    apply: (target, thisArg, argumentsList) => {
      // console.log(target, thisArg, argumentsList);
      console.log("执行了apply");
      return Reflect.apply(target, thisArg, argumentsList);
    },
    construct: (target, argumentsList, newTarget) => {
      // console.log(target, argumentsList, newTarget);
      console.log("执行了construct");
      return Reflect.construct(target, argumentsList, newTarget);
    },
    getOwnPropertyDescriptor: (target, property) => {
      // console.log(target, property);
      console.log("执行了getOwnPropertyDescriptor");
      return Reflect.getOwnPropertyDescriptor(target, property);
    },
    defineProperty: (target, property, descriptor) => {
      // console.log(target, property, descriptor);
      console.log("执行了defineProperty");
      return Reflect.defineProperty(target, property, descriptor);
    },
    getPrototypeOf: (target) => {
      // console.log(target);
      console.log("执行了getPrototypeOf");
      return Reflect.getPrototypeOf(target);
    },
    setPrototypeOf: (target, prototype) => {
      // console.log(target, prototype);
      console.log("执行了setPrototypeOf");
      return Reflect.setPrototypeOf(target, prototype);
    },
    isExtensible: (target) => {
      // console.log(target);
      console.log("执行了isExtensible");
      return Reflect.isExtensible(target);
    },
    preventExtensions: (target) => {
      // console.log(target);
      console.log("执行了preventExtensions");
      return Reflect.preventExtensions(target);
    },
    ownKeys: (target) => {
      // console.log(target);
      console.log("执行了ownKeys");
      return Reflect.ownKeys(target);
    },
    ...opts,
  });
};

const obj = {
  name: "张三",
  age: 20,
};
const fn = function () {
  return "hello";
};
const __obj = proxyFactory(obj);
const __fn = proxyFactory(fn); // apply只有当当前代理对象为函数时才会执行
const init = () => {
  // set;
  __obj.name = "李四";
  // get;
  __obj.name;
  // has;
  "name" in __obj;
  // deleteProperty;
  delete __obj.age;
  // apply;
  __fn();
  // construct;
  new __fn();
  // getOwnPropertyDescriptor;
  Object.getOwnPropertyDescriptor(__obj, "name");
  // defineProperty;
  Object.defineProperty(__obj, "name", {
    value: "王五",
  });
  // getPrototypeOf;
  Object.getPrototypeOf(__obj);
  // setPrototypeOf;
  Object.setPrototypeOf(__obj, null);
  // isExtensible;
  Object.isExtensible(__obj);
  // preventExtensions;
  Object.preventExtensions(__obj);
  // ownKeys;
  Object.getOwnPropertyNames(__obj);
  console.log(__obj, obj);
};

init();

如何实现

实现过程

了解了代理和反射的概念和用法,我们可以尝试使用ES5的语法实现一下对象属性的增删改查

首先是反射

var __Reflect = {
  set(target, prop, value) {
    target[prop] = value;
  },
  get(target, prop) {
    return target[prop];
  },
  defineProperty(target, property, descriptor) {
    return Object.defineProperty(target, property, descriptor);
  },
  deleteProperty(target, property) {
    return delete target[property];
  },
};

然后是代理

function __Proxy(target, handler) {
  var __target = {};
  this.target = target;
  this.handler = handler;
  this.init(__target);
  return __target;
}
__Proxy.prototype = {
  init(__target) {
    this.readWrite(__target);
    Object.__defineProperty = this.defineProperty.bind(this);
    Object.__delete = this.deleteProperty.bind(this);
  },
  readWrite(__target) {
    // 初始化读写函数
    var target = this.target;
    var handler = this.handler;
    for (const key in target) {
      Object.defineProperty(__target, key, {
        configurable: true,
        set(val) {
          // 新增/修改
          target[key] = val;
          return handler.set(target, key, val);
        },
        get() {
          // 读取
          return handler.get(target, key);
        },
      });
    }
  },
  defineProperty(target, property, descriptor) {
    // 定义/修改
    var __d = this.handler.defineProperty;
    var fn = typeof __d === "function" ? __d : __Reflect.defineProperty; // 如果钩子函数存在,则执行代理拦截函数
    return fn(target, property, descriptor);
  },
  deleteProperty(target, property) {
    // 删除
    var __delete = this.handler.deleteProperty;
    if (typeof __delete === "function") {
      return __delete(target, property); // 如果钩子函数存在,则执行代理拦截函数
    }
    return __Reflect.deleteProperty(target, property);
  },
};

大致介绍一下思路:我的做法是在执行属性读写,对象定义,删除前对属性进行函数拦截,将结果抛出,这里的delete由于是全局关键字执行,所以我在Object中增加了删除属性的函数用于模拟delete操作,此外还重写了一下defineProperty函数,用作对象劫持

运行效果

var obj = {
  name: "张三",
  age: 20,
};

var proxy = new __Proxy(obj, {
  set(target, prop, value) {
    console.log("set");
    return __Reflect.set(target, prop, value);
  },
  get(target, prop) {
    console.log("get");
    return __Reflect.get(target, prop);
  },
  defineProperty(target, property, descriptor) {
    console.log("defineProperty");
    return __Reflect.defineProperty(target, property, descriptor);
  },
  deleteProperty(target, property) {
    console.log("deleteProperty");
    return __Reflect.deleteProperty(target, property);
  },
});
// set
proxy.name = "李四";
// get
console.log(proxy.name);
// defineProperty
Object.__defineProperty(proxy, "name", {
  value: "王五",
});// 使用自定义的defineProperty,劫持对象操作
// delete
Object.__delete(proxy, "name");// 这个函数是模拟delete关键字的操作
console.log(proxy.name, obj.name);

应用限制及优点

限制:

  1. 上面我们也说了代理和反射是ES6新增的两个对象,兼容性上会有一些折扣;
  2. 此外使用反射操作对象和直接操作对象还是有区别的,使用反射操作对象会有性能的损失;

优点:

  1. 提高了对象的灵活性,监听对象及操作对象变得简易
  2. 拓展性变高了,可以应对更多针对对象的操作

写在最后

以上就是文章所有内容了,感谢你看到了最后,如果对文章有任何问题欢迎在评论区或私信探讨

最后如果文章对你有帮助,还希望点赞支持一下,谢谢!

示例源码:myCode: 基于js的一些小案例或者项目 - Gitee.com

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

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

相关文章

元宇宙哪些吓死人的概念话题

元宇宙哪些吓死人的概念话题 深层的奥秘:人是符号及符号的意义驱动的 趣讲大白话:现有概念再有东西 ************** **元宇宙与跨学科 2.1 元宇宙与交叉学科 2.2 元宇宙与哲学 2.3 元宇宙与文学 2.4 元宇宙与艺术学 2.5 元宇宙与电影学 2.6 元宇宙与传播…

【面向对象】构造函数与析构函数详解

构造函数与析构函数详解 文章目录构造函数类型参考博客😊点此到文末惊喜↩︎ 构造函数 类型 默认构造函数(缺省构造函数) 一个类中只能出现一个默认构造函数在调用时,不需要传入实参。因为默认构造函数通常是无参的或所有形参都…

射频信号探测器制作

射频信号探测器制作一、元件要求二、芯片参数三、原理剖析四、实验思路五、实物展示、使用六、个人总结一、元件要求 CD4001,是四2输入或非门。或非门的逻辑关系特点是只有当输入端全部为低电平时,输出端为高电平状态;在其余输入情况下&…

TCP-IP协议基础知识

1、简介 tcp/ip:通信协议的统称,是IP,ICMP,TCP,UDP,HTTP,TELNET,SNMP,SMTP等协议的集合 TCP/IP和OSI的关系 OSI属于ISO(国际化标准组织)制定的通信系统标准,但是并没有普及;TCP/IP是非ISO制定的某种国际…

详谈ORB-SLAM2的局部建图线程LocalMapping

ORB-SLAM2的局部建图线程LocalMapping分为5个步骤,非常简单。当得到缓冲队列里的关键帧,第一步处理当前关键帧的地图点关系等;第二步判断地图点是否为新创建,如果是那就进入测试,测试地图点的好坏,如果不好…

【关于Linux中----信号】

文章目录一、信号入门1.1 信号概念1.2 用 kill-l命令查看信号列表1.3 信号处理常见方式预览二、产生信号2.1 通过终端按键产生信号2.2 由于程序中存在异常产生信号2.3 系统接口调用产生信号2.4 软件条件产生信号三、阻塞信号3.1 信号相关常见概念补充3.2 在内核中的表示3.3 sig…

编码技巧——JDK版本切换/JDK7和JDK6下的文件输入输入

最近有使用低版本JDK实现文件输入输出的需求,主要是妹子计算机专业考研复试上级算法题,输入输入依赖文件而非纯算法代码,并且IDE一般使用JDK8以下的SDK,导致一些JDK8的API不适用;较早版本的JDK API代码是操作一大堆的缓…

jenkins部署过程

Jenkins 安装 示例服务器为 阿里云 CentOS 服务器。安全组中增加 8080 端口 Jenkins 默认占用 Jenkins 安装大体分两种方式,一种使用 Docker 另一种则是直接安装,示例选择后者。不管使用哪种方式安装,最终使用层面都是一样的。 Linux安装过…

Linux下源码安装nginx

一 安装步骤 nginx在linux下的源码安装,步骤还是比较简单的,主要分为以下 1. 下载安装包,这里选择:nginx-1.18.0.tar.gz 下载地址:http://nginx.org/en/download.html 2. 安装前置环境: yum install -y…

苹果手机字体大小怎么设置?简单实用,轻松学会

使用苹果手机的时候,发现苹果手机的字体看起来不是很舒服,想要将字体调大一点,却不知道怎么办。苹果手机字体大小怎么设置?其实方法很简单,今天小编就来具体的讲一下调整苹果手机字体大小的方法。 苹果手机字体大小怎么…

智能工厂中的设备如何实现远程监控和故障报警

智能工厂是在数字化工厂内,利用物联网技术和云计算计算加强设备信息管理水平,提高生产过程可控性、减少生产线人工干预,保证安全稳定的生产节奏,助力构建高效、节能、绿色、舒适、安全的工厂。 物通博联推出的智能工厂设备物联网…

C语言及算法设计课程实验四:选择结构程序设计

C语言及算法设计课程实验四:选择结构程序设计一、实验目的二、实验内容2.1、根据x的分段函数求对于的y值2.2、求小于1000正数的平方根2.3、百分制等级输出2.4、四个整数的顺序输出三、实验步骤3.1、选择结构程序设计实验题目1:根据x的分段函数求对于的y值…

四、GStreamer基础

本章介绍GStreamer的基本概念。理解这些概念对于阅读本指南的其他任何内容都是很重要的,它们都假定理解了这些基本概念。 元素 元素是GStreamer中最重要的一类对象。你通常会创建一个链接在一起的元素链,并让数据在这个元素链中流动。元素有一个特定的…

运动耳机有必要买吗、口碑最好的运动耳机品牌排行

冬天绝对是个减肥的好季节,因为这个季节天气比较冷,我们在运动过程中消耗的热量也就会更多,因此选择一款不错的运动耳机来用坚持就显得尤为重要了。这款运动耳机要能稳定在耳朵上,还要具备防水功能,同时音质上也要有保…

gd32f103vbt6 串口OTA升级-问题记录

今天研究了一下gd32单片机串口OTA升级的事情。我感觉ota的唯一好处就是不用调试器就可以下载(更新)单片机应用程序。(但是需要232串口,OTA程序我是使用stlink下载的!!) 可能有些同学要问,32的单片机本身就…

【综合】数字IC设计需要考虑的时序参数;Race Hazard;同步系统时序要求;建立时间、保持时间;偏斜;抖动;毛刺、竞争冒险;亚稳态

【综合】数字IC设计需要考虑的时序参数;Race Hazard;同步系统时序要求;建立时间、保持时间;偏斜;抖动;毛刺、竞争冒险;亚稳态数字设计时需要考虑的时序参数传播延迟 propagation delay, 上升时间…

十一、树结构的实际应用—堆排序

1、基本介绍 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最好最坏平均时间复杂度均为O(n\log n) 。也不是稳定排序。堆是具有以下性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,…

云音乐实现注册功能

1. 新建web项目 Shop_SM2. 复制Shop资源和相关代码,实现注册功能回顾当前配置下的加载顺序1.MyBatis的主配置文件mybatis-config.xml (在根目录下)和 工具类MyBatisUtil(注意总配置文件的路径)2.实体类(Use…

docker篇---pycharm连接docker,使用docker环境

pycharm连接docker,使用docker环境一、生成镜像和容器1.1 创建容器,需要加端口映射1.2 进入容器1.3 设置root密码,后续登录会用到1.4 修改配置文件1.5 重启ssh服务1.5 本机连接ssh二、pycharm连接docker2.1 允许远程客户端连接2.2 pycharm配置…

线程互斥与同步--Linux

文章目录线程互斥的概念与意义互斥的原理--原子性关于售票模拟的互斥应用死锁问题线程同步的概念与意义条件变量实现同步生产者消费者模型--互斥与同步基于阻塞队列的生产者消费者模型基于环形队列的生产者消费者模型POSIX信号量线程池线程安全下的单例模式总结线程互斥的概念与…