Proxy 与 Reflect 的常见使用场景

有以下三种:
数据绑定与观察者模式
函数参数的验证
简便的构造函数

1.数据绑定与观察者模式

实现数据绑定与观察者模式,也可以使用 ES5 新增的 Object.defineProperty()方法,Vue2 就是使用该技术实现的,而 Vue3 为什么换用 Pro 需要来实现呢?
所以现在先来对比一下这两种实现方法。

1.1 Object.defineProperty()方法实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>

  <body>
    <div id="container"></div>
    <script>
      //创建一个观察者  target为被观察的目标
      function observer(target) {
        const div = document.getElementById("container");
        const ob = {}; //创建一个观察对象
        const props = Object.keys(target);

        for (const prop of props) {
          //给ob新增属性
          Object.defineProperty(ob, prop, {
            get() {
              return target[prop];
            },
            set(val) {
              target[prop] = val;
              render();
            },
            enumerable: true,
          });
        }
        render();
        //渲染函数,将ob的属性与属性值渲染到视图上
        function render() {
          let html = "";
          for (const prop of Object.keys(ob)) {
            html += `<p><span>${prop}:</span><span>${ob[prop]}</span></p>`;
          }
          div.innerHTML = html;
        }

        return ob;
      }

      const target = {
        a: 1,
        b: 2,
      };
      const obj = observer(target);
    </script>
  </body>
</html>

1.2 Proxy 与 Reflect 方法实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>

  <body>
    <div id="container"></div>

    <script>
      //创建一个观察者
      function observer(target) {
        const div = document.getElementById("container");
        //为被观察的目标 创建一个代理对象
        const proxy = new Proxy(target, {
          set(target, prop, value) {
            Reflect.set(target, prop, value);
            render();
          },
          get(target, prop) {
            return Reflect.get(target, prop);
          },
        });
        render();
        //渲染函数
        function render() {
          let html = "";
          for (const prop of Object.keys(target)) {
            html += `<p><span>${prop}:</span><span>${target[prop]}</span></p>`;
          }
          div.innerHTML = html;
        }
        return proxy;
      }
      const target = {
        a: 1,
        b: 2,
      };
      const obj = observer(target);
    </script>
  </body>
</html>

1.3 两种实现方法的对比

Proxy 与 Reflect 主要有以下优点:
直观角度上来看,Proxy 与 Reflect 方法的代码量更少,而且直接调用了对应的 API,可阅读性也更强,更容易看懂代码。
Proxy 与 Reflect 方法不用去新增一个 ob 对象,而是只需要创建一个代理对象,这样就不会占用内存,会减少内存消耗。
最后也是最重要的一点,Proxy 与 Reflect 方法可监听目标新增的属性以及目标删除的属性,而 Object.defineProperty()方法无法实现动态监听,所以在 Vue2 中需要使用 set 以及 set 以及 set 以及 delete 方法来进行响应式数据的增加与删除。

2.函数参数的验证

由于 Javascript 是一种动态类型语言,不能像 C 语言与 Java 一样限定数据的类型,平时在进行函数传参是偶尔会遇到一定的麻烦,所以可使用 Proxy 对象的 apply 捕获器实现函数参数的验证,对参数进行审查,不符合条件的参数,给予对应的提示。
如当需要给两数相加 sum()函数限定两个参数 a,b 都为 Number 类型。

//封装的 函数参数验证 方法
function validatorFunction(func, ...types) {
  const proxy = new Proxy(func, {
    //调用目标函数时触发该捕获器
    apply(target, thisArgument, argumentsList) {
      //thisArg:调用函数时的 this 参数。
      //argumentsList:调用函数时的参数列表
      //types为传入的参数数据类型数组 当前为["number", "number"]
      types.forEach((t, i) => {
        if (typeof argumentsList[i] !== t) {
          throw new TypeError(
            `第${i + 1}个参数${argumentsList[i]}不满足类型${t}`
          );
        }
      });
      return Reflect.apply(target, thisArgument, argumentsList);
    },
  });
  return proxy;
}

//简单的两数相加函数
function sum(a, b) {
  return a + b;
}
const sumProxy = validatorFunction(sum, "number", "number");
console.log(sumProxy(1, 2));

3.简便的构造函数

因为平时使用 Class 运算符构建一个类时,需要写大量的重复代码,使用可以使用 Proxy 对象的 construct 捕获器实现代码简化,并且还可以对属性传值进行一定的限制。
如当创建一个简单的 User 类,共有 name、sex、age 这三个属性。

//封装的 简便构造函数 方法
function ConstructorProxy(Class, ...propNames) {
  return new Proxy(Class, {
    //在 new 操作符中调用该捕获器
    construct(target, argumentsList, newTarget) {
      //target:目标构造函数
      //argumentsList:传给目标构造函数的参数列表。
      //newTarget:最初被调用的构造函数。
      const obj = Reflect.construct(target, argumentsList);
      //propNames 为创建代理对象时,传入的构造函数的属性列表 目前为["name", "sex", "age"]
      propNames.forEach((name, i) => {
        obj[name] = argumentsList[i];
      });
      return obj;
    },
  });
}
//创建User类
class User {}
const UserProxy = ConstructorProxy(User, "name", "sex", "age"); //传入属性名
const obj = new UserProxy("xx", "男", 18); //创建实例,并传入属性值
console.log(obj);

//创建Phone类
class Phone {} //User {name: 'xx', sex: '男', age: 18}

const ProductProxy = ConstructorProxy(Phone, "brand", "price", "inventory");

const phone1 = new ProductProxy("华为", 4000, 100);
console.log(phone1); //Phone {brand: '华为', price: 4000, inventory: 100}
posted @   柯基与佩奇  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
欢迎阅读『Proxy 与 Reflect 的常见使用场景』
点击右上角即可分享
微信分享提示