前端学习之路(3) JavaScript中的代理(Proxy)与反射(Reflect)

定义与概念

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

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

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

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

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

属性及函数

Proxy

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

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

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

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

Reflect

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

  • get:用于读取一个对象的属性值
  • set:用于设置一个对象的属性值
  • has:用于判断一个对象是否有某个属性
  • deleteProperty:用于删除一个对象的属性
  • apply:当前代理对象为函数,用于调用当前对象的方法
  • construct:用于通过构造函数创建一个新的对象实例
  • getOwnPropertyDescriptor:用于读取一个对象的自身属性描述对象
  • defineProperty: 用于为一个对象定义一个属性
  • getPrototypeOf:用于读取一个对象的原型对象
  • setPrototypeOf:用于设置一个对象的原型对象
  • isExtensible:用于判断一个对象是否可扩展
  • preventExtensions: 用于防止一个对象被扩展
  • 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();

image.png

如何实现

实现过程

了解了代理和反射的概念和用法,我们可以尝试使用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);

image.png

应用限制及优点

限制:

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

优点:

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

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

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

相关文章

【开源】基于JAVA+Vue+SpringBoot的教学资源共享平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 课程档案模块2.3 课程资源模块2.4 课程作业模块2.5 课程评价模块 三、系统设计3.1 用例设计3.2 类图设计3.3 数据库设计3.3.1 课程档案表3.3.2 课程资源表3.3.3 课程作业表3.3.4 课程评价表 四、系统展…

如何在Python中设置HTTP代理:探秘网络世界的“魔法门“

嗨,各位Python的魔法师们!今天,我们要探索如何在Python中设置HTTP代理,让我们的网络请求飞得更远! 首先,我们要明白什么是HTTP代理。简单说,它就像一个中转站,帮我们转发请求给目标…

双非本科准备秋招(14.3)—— java线程

创建和运行线程 1、使用Thread Slf4j(topic "c.Test1")public class Test1 {public static void main(String[] args) {Thread t new Thread("t1") {Overridepublic void run() {log.debug("running");}};t.start();​log.debug("runnin…

LeetCode.1686. 石子游戏 VI

题目 题目链接 分析 本题采取贪心的策略 我们先假设只有两个石头a,b, 对于 Alice 价值分别为 a1,a2, 对于 Bob 价值而言价值分别是 b1,b2 第一种方案是 Alice取第一个,Bob 取第二个,Alice与Bob的价值差是 c1 a1 - b1&#xf…

Unity3d Shader篇(二)— 片元漫反射着色器解析

文章目录 前言一、片元漫反射着色器是什么?1. 片元漫反射着色器的工作原理2. 顶点漫反射着色器和片元漫反射着色器的比较顶点漫反射着色器优点:缺点: 片元漫反射着色器优点:缺点: 二、使用步骤1. Shader 属性定义2. Su…

【奶奶看了都会】《幻兽帕鲁》云服务器部署教程

在帕鲁的世界,你可以选择与神奇的生物「帕鲁」一同享受悠闲的生活,也可以投身于与偷猎者进行生死搏斗的冒险。帕鲁可以进行战斗、繁殖、协助你做农活,也可以为你在工厂工作。你也可以将它们进行售卖,或肢解后食用。 《幻兽帕鲁》官…

PostgreSQL 也很强大,为何在中国大陆,MySQL 成为主流,PostgreSQL 屈居二线呢?

问题: PostgreSQL 也很强大,为何在中国大陆,MySQL 成为主流,PostgreSQL 屈居二线呢?PostgreSQL 能否替代 MySQL? 当我们讨论为何 MySQL 在中国大陆成为主流而 PostgreSQL 屈居二线时, 我们其实…

在conda 虚拟环境中快速卸载安装包(操作详解)

手动卸载虚拟环境中的安装包 1.卸载已经安装的安装包(不指定版本好) pip uninstall 包名 2.卸载指定的安装包 pip uninstall 包名版本号 注意 “” 不是 “” 批量快速卸载 写一个txt文件,例如aaa.txt。官网一般是requirements.txt&…

NLP_统计语言模型的发展历程

文章目录 统计语言模型发展的里程碑: 上半部分是语言模型技术的进展;下半部分则是词向量(词的表示学习)技术的发展。其中,词向量表示的学习为语言模型提供了更高质量的输入信息(词向量表示) 1…

AI新工具(20240203) 文心一言APP数字分身;HuggingChat Assistants等

文心一言APP数字分身-一键生成专属数字分身 文心一言数字分身是一项新功能,用户只需一张照片和录制三句语音,就能创建一个专属的数字分身。这个数字分身还支持个性化定义名称、声音、MBTI性格等,用户可以选择是否公开自己的数字分身。这个功…

11 插入排序和希尔排序

1. 插入排序 基本思想 直接插入排序是一种简单的插入排序法,基本思想: 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 在玩扑克牌时,就用…

虚拟存储器

第五章:虚拟存储器 常规存储管理方式的特征 一次性 驻留性 局部性原理 程序在执行时将呈现出局部性特征,即在一较短的时间内,程序的执行仅局限于某个部分,相应地,它所访问的存储空间也局限于某个区域 时间局限性 …

创建一个Vue项目(含npm install卡住不动的解决)

目录 1 安装Node.js 2 使用命令提示符窗口创建Vue 2.1 打开命令提示符窗口 2.2 初始Vue项目 2.2.1 npm init vuelatest 2.2.2 npm install 3 运行Vue项目 3.1 命令提示符窗口 3.2 VSCode运行项目 1 安装Node.js 可以看我的这篇文章《Node.js的安装》 2 使用命令提示…

【定位·HTML】

定位布局可以分为以下四种: 静态定位(inherit) 相对定位(relative) 绝对定位(absolute) 固定定位(fixed) 一般的标签元素不加任何定位属性时,默认都属于静态…

STM32标准库——(9)TIM编码器接口

1.编码器接口简介 Encoder Interface 编码器接口编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度每个高级定时器和通用定…

linux ln命令-linux软链接、硬链接-linux软、硬链接的区别(二):软链接

0、序 上一篇:linux ln命令-linux软链接、硬链接-linux软、硬链接的区别(一):硬链接 描述了硬链接相关内容,本篇主要描述软链接。 1、软链接 符号链接也称软链接,是将一个路径名链接到一个文件。这些文件是一种特别类型的文件。…

已解决!AttributeError: ‘Sequential‘ object has no attribute ‘session‘ 问题

博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通Golang》…

【Android新版本兼容】onBackPressed()方法被弃用的解决方案

提示:此文章仅作为本人记录日常学习使用,若有存在错误或者不严谨得地方欢迎指正。 文章目录 一、使用 AndroidX API 实现预测性返回手势1.1 添加依赖1.2 启用返回手势1.3 注册OnBackPressedCallback()方法来处理返回手势 一、使用 AndroidX API 实现预测…

React | Center 组件

在 Flutter 中有 Center 组件,效果就是让子组件整体居中,挺好用。 React 中虽然没有对应的组件,但是可以简单封装一个: index.less .container {display: flex;justify-content: center;align-items: center;align-content: ce…

京东微前端框架MicroApp简介

一、MicroApp 1.1 MicroApp简介 MicroApp是由京东前端团队推出的一款微前端框架,它从组件化的思维,基于类WebComponent进行微前端的渲染,旨在降低上手难度、提升工作效率。MicroApp无关技术栈,也不和业务绑定,可以用于任何前端框架。 官网链接:https://micro-zoe.gith…