一、简介
const proxy = new Proxy(target, handler);
Proxy 对象用于修改某些操作的默认行为(如属性查找、赋值、枚举、函数调用等)
- 是在在语言层面做出修改
- 属于一种 元编程(Meta Programming),即对编程语言进行编程
- Proxy 可以理解成,在目标对象之前架设一层 拦截,外界对该对象的访问,都必须先通过这层拦截
二、参数
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
?
首先,看看这段代码的工作方式:
target.foo()
:这个调用直接在target
对象上触发,所以this === proxy
时会是false
。这是因为target
只是普通对象,它本身并没有代理。
proxy.foo()
:当你调用proxy.foo()
时,JavaScript 引擎会首先查找foo
方法。由于handler
是一个空对象,默认的行为是没有拦截器,所以它会调用target.foo
方法(这是代理的默认行为)。但是,在调用target.foo()
时,this
的指向会被代理对象proxy
替代。这是因为 Proxy 会在调用方法时将this
绑定为代理对象。
Proxy
的 handler
默认行为
- 默认情况下,
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 中拦截方法,不限于
apply
、ownKeys
、deleteProperty
、has
等等是Object.defineProperty
不具备的
Proxy 的劣势:
Proxy 的劣势就是兼容性问题,而且无法用 Polyfill 磨平。