Proxy 和 Reflect

前言:Proxy 和 Reflect是配套出现使用的。虽然Proxy可以不适用Reflect也能实现系统的功能,但是会增加代码复杂度,也增加了代码理解难度。

https://mp.weixin.qq.com/s/G6maIUZONMWHxJG_E1_f-w

Proxy

一、基本语法:

const proxy = new Proxy(target, handler);
  • target:目标对象,即被代理的对象。
  • handler:处理程序对象,定义了代理对象的方法,用于拦截和定义目标对象的操作。
const target = { name: '小明', age: 18 };

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,小明
proxy.age = 19; // 输出:设置了属性:age,值为:19
console.log(proxy.age); // 输出:访问了属性:age,19

总结:proxy 代理 使得对 target 的操作,全部作用到 handler 同名函数中了。

二、实践中的使用:  开发中,什么情况使用proxy好呢

  • 数据绑定与观察者模式:vue3 的响应式就是 基于 proxy 实现的。
  • 表单验证:  我们可以使用 Proxy 和 Reflect 实现表单验证,在设置对象属性时进行校验
    const form = { username: '', age: 0 };
    
    const handler = {
      set(target, prop, value, receiver) {
        if (prop === 'age' && (typeof value !== 'number' || value <= 0)) {
          throw new TypeError('Age must be a positive number');
        }
        return Reflect.set(target, prop, value, receiver);
      }
    };
    
    const proxy = new Proxy(form, handler);
    
    try {
      proxy.age = -5; // 抛出错误:Age must be a positive number
    } catch (e) {
      console.error(e.message);
    }
    
    proxy.age = 30; // 设置成功
    console.log(proxy.age); // 输出:30

 

  • 扩展对象功能:使用 Proxy 可以动态地扩展对象功能。例如,可以在现有对象上添加日志记录功能,而不需要修改对象的原始代码。
    const original = {
      greet() {
        console.log('Hello!');
      }
    };
    
    const handler = {
      apply(target, thisArg, argumentsList) {
        console.log(`调用了方法:${target.name}`);
        return Reflect.apply(target, thisArg, argumentsList);
      }
    };
    
    const proxy = new Proxy(original.greet, handler);
    
    proxy(); // 输出:调用了方法:greet,Hello!
  • 方法劫持: 方法劫持可以用于调试、性能监控或权限控制。例如,在调用某个方法前后插入自定义逻辑。
    const service = {
      fetchData() {
        console.log('Fetching data...');
        // 模拟数据获取
      }
    };
    
    const handler = {
      apply(target, thisArg, argumentsList) {
        console.log('开始调用 fetchData');
        const result = Reflect.apply(target, thisArg, argumentsList);
        console.log('结束调用 fetchData');
        return result;
      }
    };
    
    const proxy = new Proxy(service.fetchData, handler);
    
    proxy(); // 输出:开始调用 fetchData,Fetching data...,结束调用 fetchData
  • API 请求拦截: 我们还可以使用 Proxy 和 Reflect 实现 API 请求的拦截和日志记录
    const api = {
      fetchData(endpoint) {
        console.log(`Fetching data from ${endpoint}`);
        // 模拟 API 请求
        return { data: 'Sample Data' };
      }
    };
    
    const handler = {
      apply(target, thisArg, argumentsList) {
        console.log(`调用了方法:${target.name}`);
        return Reflect.apply(target, thisArg, argumentsList);
      }
    };
    
    const proxy = new Proxy(api.fetchData, handler);
    
    const data = proxy('/api/data'); // 输出:调用了方法:fetchData Fetching data from /api/data
    console.log(data); // 输出:{ data: 'Sample Data' }

 


Reflect

一、为什么要使用Reflect这个全局对象:https://blog.csdn.net/qdmoment/article/details/91413951

  Reflect 是 ES6 为了操作对象引入的 API 。

  1、将 Object 对象一些内部的方法,放到Reflect对象上。比如:Object.defineProperty

    说明:现阶段这些方法存在于Object和Reflect对象上,未来只存在于Reflect对象上。【个人猜测:可能这样处理对对象的操作更优雅】

    意义:也就是说,从Reflect对象上可以拿到语言内部的方法。

  2、操作对象时出现报错返回false,而不是报错:【可以避免过多的使用 try...catch...,尤其是在框架层面上优势更明显】

    说明:比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

// 老写法
try {
  Object.defineProperty(target, property, attributes);
  // success
} catch (e) {
  // failure
}
 
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

  3、让操作对象的编程变为函数式编程

    说明:老写法有的是命令式编程,比如下面这个例子

// 老写法
'assign' in Object // true
 
// 新写法
Reflect.has(Object, 'assign') // true

  4、保持和proxy对象的方法一一对应    https://www.zhihu.com/question/460133198【有说明为什么要Reflect来实现。就是 简化了捕获器中,透传语义的写法

    说明:Reflect主要是和Proxy配对使用的。只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。【即 proxy对象上的操作,透传到源对象上操作】

Proxy(target, {
  set: function(target, name, value, receiver) {
    return Reflect.set(...arguments);  // 这里 使用Reflect的方法,和 Proxy 对象里面 捕获器 的方法是对应的。
  }
});

 

总结:综上所述,Reflect对象有4个意义:

  1. 「默认行为的一致性」Reflect 对象提供了与大多数 Proxy traps 对应的方法,使得在进行对象操作时,可以保持一致的编程模式,且代码的可读性和可维护性更强。
  2. 「更好的错误处理」Reflect 方法返回一个布尔值,可以清晰地指示操作是否成功,这使得错误处理更加直观。(更安全的操作 对象,不会静默操作)
       Reflect 方法在遇到非法操作时会抛出适当的异常,如TypeError或RangeError,而不是默默地失败。这种严格的错误处理有助于在开发阶段尽早发现潜在问题。
  3. 「函数式编程风格」Reflect 方法接受目标对象作为第一个参数,这允许你以函数式编程风格处理对象操作。
  4. 「接收者(receiver)参数」Reflect 方法通常接受一个接收者参数,这允许你在调用方法时明确指定 this 的值,这在实现基于原型的继承和自定义 this 绑定时非常有用。

 Reflect 实践中的作用: Reflect 可以看作弥补 Object 对象上不足,而创造出来的。所以 Reflect具有的属性方法都是 Object 内置的属性方法。

  • 用Reflect取代 in、delete 这种操作对象的方式。
  • 返回值一致性:Reflect 方法通常返回一个布尔值来表示操作的成功与否,或者返回与操作相关的具体结果。这与一些Object类型上的方法可能返回undefined或静默失败不同,提高了代码的可预测性和调试性。 

二、Reflect常用的API:https://www.bilibili.com/video/BV1Su411979o?p=2&spm_id_from=pageDriver

  1、Reflect对象一共有13个静态方法:

    • Reflect.apply(target, thisArg, args)         相当于  Function.prototype.apply(thisArg, args)
    • Reflect.construct(target, args)                             New 构造函数()
    • Reflect.get(target, name, receiver)                      obj.prop 或 obj['prop']
    • Reflect.set(target, args, value, receiver)  
    • Reflect.defineProperty()
    • Reflect.deleteProperty()                                       delete obj.prop  或 delete obj['prop']
    • Reflect.has()                                                         'prop' in obj
    • Reflect.ownKeys()
    • Reflect.isExtensible()
    • Reflect.preventExtensions()
    • Reflect.getOwnPropertyDescriptor()
    • Reflect.getPrototypeOf()
    • Reflect.setPrototypeOf()

 

posted @ 2022-03-02 09:17  吴飞ff  阅读(206)  评论(0编辑  收藏  举报