Proxy 与 Reflect 的常见使用场景

Proxy 与 Reflect 的常见使用场景

首先对代理与反射还不熟悉的朋友可以看看我的上一篇博客详解 JS 中的 Proxy(代理)和 Reflect(反射)
这篇博客只讲 Proxy 的应用场景,分别有以下三种:

  • 数据绑定与观察者模式
  • 函数参数的验证
  • 简便的构造函数

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 中我们需要使用 s e t 以及 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}

结语

这是我目前所了解的知识面中最好的解答,当然也有可能存在一定的误区。

所以如果对本文存在疑惑,可以在评论区留言,我会及时回复的,欢迎大家指出文中的错误观点。

最后码字不易,觉得有帮助的朋友点赞、收藏、关注走一波。

猜你喜欢

转载自blog.csdn.net/forward_xx/article/details/127274265