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

本文介绍了JavaScript中的Proxy和Reflect特性,它们允许开发者在运行时动态创建代理对象,控制对象的访问并提供各种钩子函数。Proxy和Reflect提供了一种灵活的方式来操作和监控对象,尽管存在兼容性和性能考虑,但它们极大地增强了对象的灵活性和可扩展性。
摘要由CSDN通过智能技术生成

定义与概念

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新增的两个对象,兼容性上会有一些折扣;
  • 此外使用反射操作对象和直接操作对象还是有区别的,使用反射操作对象会有性能的损失;

优点:

  • 提高了对象的灵活性,监听对象及操作对象变得简易
  • 拓展性变高了,可以应对更多针对对象的操作
  • 9
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你好!关于"vue3proxy结合reflect"的问题,我理解你可能想了解如何在Vue 3使用Proxy与Reflect结合起来。Vue 3是一个流行的JavaScript框架,而Proxy和Reflect则是ES6新增的特性。 在Vue 3,可以使用Proxy对象来代理Vue实例,以便捕获对数据的访问和修改。Proxy对象可以拦截并处理对Vue实例的操作,比如读取、设置和删除属性等。而Reflect对象提供了一组用于操作对象的方法,比如Reflect.get()、Reflect.set()和Reflect.deleteProperty()等。 结合使用Proxy和Reflect可以提供更灵活的控制和监控能力。你可以通过在Vue实例上创建一个代理对象,来拦截对数据的访问和修改,并在代理处理器使用Reflect方法来操作实际的数据。这样可以轻松地实现对数据的拦截、校验、代理等功能。 下面是一个简单的示例代码,展示了如何在Vue 3使用Proxy和Reflect结合起来: ```javascript const data = { message: 'Hello, Vue!', }; const proxy = new Proxy(data, { get(target, key) { console.log('Getting ' + key); return Reflect.get(target, key); }, set(target, key, value) { console.log('Setting ' + key + ' to ' + value); return Reflect.set(target, key, value); }, }); // 创建Vue应用 const app = Vue.createApp({ data() { return proxy; // 使用代理对象 }, }); app.mount('#app'); ``` 在上面的代码,我们创建了一个名为data的普通对象,并使用Proxy对象创建了一个代理对象proxy。在代理对象的get和set处理器,我们分别使用Reflect.get和Reflect.set来操作实际的数据。这样,当我们通过Vue实例访问或修改数据时,会触发代理处理器,并通过Reflect方法操作实际的数据。 希望这个例子能帮助到你,如果还有其他问题,请随时提问!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值