• 技术文章 >web前端 >Vue.js

    不会vue没事,但Proxy一定得会!

    藏色散人藏色散人2023-04-14 17:23:30转载122

    在几个月前就有写这篇文章的想法了,但是想到这个知识点 VueJs 用得比较多,我本身是一个 React 开发者,如果让我选另外一个框架的话我可能更倾向 angular,但是最近在学 NestJs,发现底层大量使用的 Reflect,具体如下:

    image.png

    好了,到了这里我就是我今天为什么会写这篇文章的原因了,本篇文章将会按照 Object -> Proxy -> Reflect 的顺序进行讲解,赶紧学起来吧!

    Object

    ES5 中,我们可以定义一个对象,并且对其进行操作(添加或查找),如下代码所示:

    const moment = {
      age: 18,
      address: "西安",
    };
    
    moment["hobby"] = "basketball";
    moment.hight = 1.59;
    
    console.log(moment.age); // 18

    那如果我们要对该对象进行监听呢,我们希望可以监听到这个对象中的属性被设置或获取的过程,我们可以通过属性描述符中的存储属性来做到这个功能。

    属性描述符

    ES5 中,所有的属性都具备了属性描述符,具体使用如下图所示:

    image.png

    就像上图所展示的一样,这个普通的对象属性对应的属性描述符,可不仅仅是一个18,它还包含另外三个特性,它们分别是:

    • writable;
    • enumerable;
    • configurable;

    在创建普通户型时,属性描述符会使用默认值,我们可以使用 Object.defineProperty(...) 来添加一个新属性或者修改一个已有属性(当该属性 Configurable 的值为 true 时)并对特性进行设置,具体如下代码所示:

    const moment = {
      age: 18,
      address: "西安",
    };
    
    Object.defineProperty(moment, "address", {
      value: "肇庆",
      writable: true,
      configurable: true,
      enumerable: true,
    });
    
    console.log(moment.address); // 肇庆

    该方法接收三个参数,它们分别为:

    • 要定义属性的对象;
    • 要定义或修改的属性的名称或 Symbol;
    • 要定义或修改的属性描述符;

    Getter和Setter

    前面说了这么多基础的东西,但是还没有讲解是怎么接收到属性的变化的,在这里,属性描述符 Object.defineProperty 提供了两个属性,它们分别是 setget,两个属性的使用如下所示:

    const moment = {
      age: 18,
      address: "西安",
    };
    
    Object.keys(moment).forEach((key) => {
      let value = moment[key];
      Object.defineProperty(moment, key, {
        get: function () {
          console.log(`监听到了对象的 ${key} 属性被访问了`);
          return value;
        },
    
        set: function (params) {
          console.log(`监听到了对象的 ${key} 属性被修改了`);
          value = params;
        },
      });
    });
    
    moment.age = 22;
    const foo = moment.address;

    当我们对 moment.age 进行赋值的时候,会调用 set 属性上的方法,最终会输出 监听到了对象的 age 属性被修改了

    当我们对 moment.address 进行取值的时候,会调用 get 属性上的方法,最终会输出 监听到了对象的 address 属性被访问了

    虽然这两个方法是能做到,但是如果我们想监听更加丰富的操作的时候是做不到的,例如新增属性,淡出属性,使用 Object.defineProperty(...) 是做不到的

    image.png

    最终结果如上图所示,终端中的输出是对应上面画圈圈的代码执行。

    那么 Proxy 的出现就很好的解决了这一痛点。

    Proxy

    ES6 中,新增了一个 Proxy 类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的,它是一种由你创建的特殊对象,它封装另一个普通对象或者说挡在这个普通对象的前面,例如你要修改一个对象,它主要有以下的流程图:

    image.png

    它就像一个关卡对你的操作进行拦截。

    这个类的基本使用如下所示:

    const moment = {
      age: 18,
      address: "西安",
    };
    
    const proxy = new Proxy(moment, {});
    
    console.log(proxy); // { age: 18, address: '西安' }

    你会发现它返回的是同一个对象,但是内存地址不同,所以通过严格相等比较返回的值也就是 false

    Peoxy的13种捕获器

    Peoxy 总共有13个捕获器,但是常用的就那么几个,这里不低其全部讲解,具体可以查看 MDN官网

    话不多说,直接上代码:

    const moment = {
      age: 18,
      address: "西安",
    };
    
    function foo(x, y) {
      return x + y;
    }
    
    const proxy = new Proxy(moment, {
      has: function (target, prop) {
        console.log(`使用 in 访问了 ${prop} 是否存在于 moment 对象`);
      },
      get: function (target, property, receiver) {
        console.log(`通过读取 moment 对象中的 ${property} 属性`);
      },
      set: function (target, property, value, receiver) {
        console.log(`通过设置 moment 对象中的 ${property} 属性为 ${value}`);
      },
    });
    
    const fProxy = new Proxy(foo, {
      apply: function (target, _, params) {
        return target(...params) * 10;
      },
      construct: function (target, argumentsList, newTarget) {
        console.log(target); // [Function: foo]
        console.log(argumentsList); // [ 1, 2 ]
        console.log(newTarget); // [Function: foo]
        return {};
      },
    });
    
    "age" in proxy; // 使用 in 访问了 age 是否存在于 moment 对象
    proxy.age; // 通过读取 moment 对象中的 age 属性
    proxy.address = "肇庆"; // 通过设置 moment 对象中的 address 属性为 肇庆
    
    console.log(foo(1, 2)); // 3
    console.log(fProxy(1, 2)); // 30
    
    new fProxy(1, 2);

    在上面的代码中,target === momentreceiver === proxy 都返回 true,也就是说 target 为对应要修改的对象,而 receiverProxy 或者继承 Proxy 的对象。

    操作 Proxy 的同时会修改 moment 对象。

    可取消代理

    普通对象总是陷入到目标对象,并且在创建之后不能改变,只要还保持着对这个代理的引用,代理的机制就将维持下去。

    但是可能会存在这样的情况,比如你想要创建一个在你想要停止它作为代理时便可被停用的代理,解决的方案是创建可取消代理,具体代码如下所示:

    const moment = {
      age: 18,
      address: "西安",
    };
    
    const { proxy, revoke } = Proxy.revocable(moment, {
      get: function (target, key, receiver) {
        console.log("get 捕获器");
      },
    });
    
    proxy.address;
    revoke();
    proxy.address;

    最终的输出如下图所示:

    image.png

    一旦可取消代理被取消,任何对他的访问都会抛出 TypeError 错误。

    Proxy的问题与不足

    尽管现在 Proxy 已经做得很好了,但是在某些情况下,代理也不能与现在的 ECMAScript 机制很好地协同。

    Proxy中的this

    Peoxy 潜在的一个问题来源是 this 值,我们知道方法中的 this 通常执行调用这个方法的对象,具体代码如下所示:

    const target = {
      moment() {
        return this === proxy;
      },
    };
    
    const proxy = new Proxy(target, {});
    
    console.log(target.moment()); // false
    console.log(proxy.moment()); // true

    按照正常的理解这是没有问题的调用 Proxy 上的任何方法,而这个方法进而又会调用另一个方法,实际上都会调用 Proxy 内部的方法,这是符合预期的行为,但是,如果目标对象依赖于对象表示,那就可能碰到意料之外的问题。

    举个例子:

    const wm = new WeakMap();
    
    class User {
      constructor(userId) {
        wm.set(this, userId);
      }
    
      set id(userId) {
        wm.set(this, userId);
      }
      get id() {
        return wm.get(this);
      }
    }

    由于这个实现依赖 User 实例的对象标识,在这个实例被代理的情况下就会出现问题:

    const user = new User(1);
    console.log(user.id); // 1
    
    const proxy = new Proxy(user, {});
    console.log(proxy.id); // undefined

    这是因为 User 实例一开始使用目标对象作为 WeakMap 的键,代理对象却尝试从自身取得这个实例,要解决这个问题,就需要重新设置代理,把代理 User 实例改为代理 User 类本身,之后再创建代理的实例就会创建代理实例作为 WeakMap 的键了:

    const UserProcess = new Proxy(User, {});
    const proxy = new UserProcess(1);
    console.log(proxy.id); // 1

    Proxy与内部槽位

    代理与内置引用类型的实例通常可以很好地协同,但有些 ECMAScript 内置类型可能会依赖代理无法控制的机制,结果导致在代理上调用某些方法会出错,具体代码如下所示:

    const target = new Date();
    
    const proxy = new Proxy(target, {});
    
    console.log(proxy instanceof Date); // true
    proxy.getDate(); // TypeError: this is not a Date object.

    在上面的代码中,根据 ECMAScript 规范,Date 类型方法的执行依赖 this 值上的内部曹伟 [[NumberDate]],代理对象上不存在这个内部槽位,而且这个内部槽位的值也不能通过普通的 get()set() 操作访问到,于是代理拦截后本应该转发给目标对象的方法会抛出 TypeRrror 的错误。

    这篇文章到此结束,下一篇将会详细讲解 Reflect,敬请期待。

    以上就是不会vue没事,但Proxy一定得会!的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:juejin,如有侵犯,请联系admin@php.cn删除
    专题推荐:Vue
    上一篇:聊聊vue3中watch和computed的使用方法 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • 如何利用rancher2来部署vue项目• 如何使用Vue的过滤器功能来实现模糊搜索• vue兄弟组件间怎么进行通信?方法详解• thinkphp和vue怎么实现交互• 如何在ThinkPHP和Vue之间实现跨域
    1/1

    PHP中文网