js中代理Proxy的使用

一、简介

const proxy = new Proxy(target, handler);
Proxy 对象用于修改某些操作的默认行为(如属性查找、赋值、枚举、函数调用等)
  • 是在在语言层面做出修改
  • 属于一种 元编程(Meta Programming),即对编程语言进行编程
  • Proxy 可以理解成,在目标对象之前架设一层 拦截,外界对该对象的访问,都必须先通过这层拦截

二、参数

notion image
Proxy 这个词的原意是 代理,用在这里表示由它来 代理 某些操作,可以译为 代理器
  • target:被 Proxy 处理虚拟化的对象,它常被作为代理的存储后端,根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)
  • handler:包含捕捉器(Trap)的占位符对象,可译为处理器对象
  • traps:提供属性访问的方法,这类似于操作系统中捕获器的概念

三、基本使用

const proxy = new Proxy( {}, { get: function (target, property, receiver) { console.log(`Getting ${property}!`); return Reflect.get(target, property, receiver); }, set: function (target, proxyKey, value, receiver) { console.log(`Setting ${property}!`); return Reflect.set(target, property, value, receiver); }, } );

四、代理的引用上下文问题

虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是 Proxy 代理的情况下,目标对象内部的 this 关键字会指向 Proxy 代理。
const target = { foo: function () { console.log(this === proxy); }, }; const handler = {}; const proxy = new Proxy(target, handler); console.log(target.foo()); // false console.log(proxy.foo()); // true
上面代码中,一旦 proxy 代理 target.foo,后者内部的 this 就是指向 proxy,而不是 target

为什么 proxy.foo() 会返回 true

首先,看看这段代码的工作方式:
  1. target.foo():这个调用直接在 target 对象上触发,所以 this === proxy 时会是 false。这是因为 target 只是普通对象,它本身并没有代理。
  1. proxy.foo():当你调用 proxy.foo() 时,JavaScript 引擎会首先查找 foo 方法。由于 handler 是一个空对象,默认的行为是没有拦截器,所以它会调用 target.foo 方法(这是代理的默认行为)。但是,在调用 target.foo() 时,this 的指向会被代理对象 proxy 替代。这是因为 Proxy 会在调用方法时将 this 绑定为代理对象。

Proxyhandler 默认行为

  • 默认情况下,Proxy 会直接将目标对象的方法调用代理到目标对象本身,而 this 会指向代理对象而不是目标对象。因此,在 proxy.foo() 调用时,this === proxy 会是 true

五、嵌套

Proxy 也是 不支持嵌套 的,这点和 Object.defineProperty() 是一样的。因此与需要通过逐层遍历来解决。Proxy 的写法是在 get 里面递归调用 Proxy 并返回。
// 需要代理的数据 const data = { info: { name: 'Eason', blogs: ['Webpack', 'Babel', 'React'], }, }; // 处理器对象 const handler = { get(target, key, receiver) { console.log('GET', key); // 递归创建并返回 if (typeof target[key] === 'object' && target[key] !== null) { return new Proxy(target[key], handler); } return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log('SET', key, value); return Reflect.set(target, key, value, receiver); }, }; const proxy = new Proxy(data, handler); // 以下两段代码能够进入 set proxy.info.name = 'Zoe'; proxy.info.blogs.push('proxy');

六、Proxy 与 Object.defineProperty

ES5 提供了 Object.defineProperty 方法,该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
Object.defineProperty 的三个主要问题:
  • 无法监听数组变化,Vue 通过 Hack 改写八种数组方法实现
  • 只能劫持对象的属性,因此对需要双向绑定的属性需要显式地定义
  • 必须深层遍历嵌套的对象
与 Proxy 的区别:
  • Proxy 可以直接监听数组的变化
  • Proxy 可以直接监听对象而非属性
  • Proxy 直接可以劫持整个对象,并返回一个新的对象,不管是操作便利程度还是底层功能上都远强于 Object.defineProperty
  • Proxy 有多达 13 中拦截方法,不限于 applyownKeysdeletePropertyhas 等等是 Object.defineProperty 不具备的
Proxy 的劣势:
Proxy 的劣势就是兼容性问题,而且无法用 Polyfill 磨平。