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

简介: 精读JavaScript中的代理(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. 拓展性变高了,可以应对更多针对对象的操作

写在最后

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

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


相关文章
|
6月前
|
缓存 JavaScript 数据安全/隐私保护
js开发:请解释什么是ES6的Proxy,以及它的用途。
`ES6`的`Proxy`对象用于创建一个代理,能拦截并自定义目标对象的访问和操作,应用于数据绑定、访问控制、函数调用的拦截与修改以及异步操作处理。
71 3
|
6月前
|
API
在vite.config.js 配置代理
在vite.config.js 配置代理
663 2
|
3月前
|
编解码 JavaScript 前端开发
JS逆向浏览器脱环境专题:事件学习和编写、DOM和BOM结构、指纹验证排查、代理自吐环境通杀环境检测、脱环境框架、脱环境插件解决
JS逆向浏览器脱环境专题:事件学习和编写、DOM和BOM结构、指纹验证排查、代理自吐环境通杀环境检测、脱环境框架、脱环境插件解决
104 1
|
3月前
|
JavaScript 前端开发 开发者
Vue.js 响应式变革来袭!结合热点技术,探索从 Object.defineProperty 到 Proxy 的奇妙之旅,触动你的心
【8月更文挑战第30天】在 Vue.js 中,响应式系统自动追踪并更新数据变化,极大提升了开发体验。早期通过 `Object.defineProperty` 实现,但存在对新旧属性处理及数组操作的局限。Vue 3.0 引入了 `Proxy`,克服了上述限制,提供了更强大的功能和更好的性能。实践中,可根据项目需求选择合适的技术方案,并优化数据操作,利用懒加载等方式提升性能。
41 0
|
4月前
|
前端开发 JavaScript Linux
若依修改之后,无法访问前端项目如何解决,只能访问后端的接口,我的接口8083,端不显示咋解决?在vue.config.js文件中的映射路径要跟后端匹配,到软件商店里找到Ngnix配置代理,设80不用加
若依修改之后,无法访问前端项目如何解决,只能访问后端的接口,我的接口8083,端不显示咋解决?在vue.config.js文件中的映射路径要跟后端匹配,到软件商店里找到Ngnix配置代理,设80不用加
|
6月前
|
JSON JavaScript 前端开发
vue2_vite.config.js的proxy跨域配置和nginx配置代理有啥区别?
vue2_vite.config.js的proxy跨域配置和nginx配置代理有啥区别?
210 1
|
6月前
|
前端开发 JavaScript API
前端 JS 经典:Proxy 和 DefineProperty
前端 JS 经典:Proxy 和 DefineProperty
91 0
|
6月前
|
数据采集 JavaScript 前端开发
利用axios库在Node.js中进行代理请求的实践
利用axios库在Node.js中进行代理请求的实践
|
6月前
|
JSON 前端开发 JavaScript
Webpack【Webpack图片处理、Webpack中proxy代理 、自动清理dist、Webpack优化、JavaScript中的代码检查】(三)-全面详解(学习总结---从入门到深化)
Webpack【Webpack图片处理、Webpack中proxy代理 、自动清理dist、Webpack优化、JavaScript中的代码检查】(三)-全面详解(学习总结---从入门到深化)
72 0
Webpack【Webpack图片处理、Webpack中proxy代理 、自动清理dist、Webpack优化、JavaScript中的代码检查】(三)-全面详解(学习总结---从入门到深化)
|
6月前
|
JavaScript 前端开发 API
Python之JavaScript逆向系列——通过IP代理高频获取全篇小说
Python之JavaScript逆向系列——通过IP代理高频获取全篇小说
75 0