Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Proxy用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。
ES6原生提供了Proxy构造函数,用来生成Proxy实例。
var proxy = new Proxy(target, handler);
- target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
- handler: 一个通常以函数(钩子:“traps”,即拦截操作的方法)作为属性的对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。(比如
get
钩子用于读取target
属性,set
钩子写入target
属性等等。) - p 是代理后的对象。当外界每次对 p 进行操作时,就会执行 handler 对象上的一些方法。
首先看handler是空对象(没有任何钩子)的情况:
// 在以下例子中,我们target使用了一个原生 js 对象,所有对p的操作都直接转发给 target上。
let target = {};
let p = new Proxy(target, {});
p.a = 37; // 操作转发到目标
console.log(target.a); // 37. 操作已经被正确地转发
我们可以看到,没有任何钩子,
proxy
是一个target
的透明包装.Proxy
是一种特殊的“奇异对象”。它没有自己的属性。如果handler
为空,则透明地将操作转发给target
。
再看handler有一个get钩子的示例:
// 在以下简单的例子中,当对象中不存在属性名时,默认返回值为 37。下面的代码以此展示了 get handler 的使用场景。
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 37;
}
};
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
Proxy对象的所有用法,都是上面的这种形式。不同的只是handle参数的写法。
其中new Proxy用来生成Proxy实例,target是表示所要拦截的对象,handle是用来定制拦截行为的对象。
有哪些对对象的基本操作的拦截操作?
Proxy共有13种劫持操作,handler代理的一些常用的方法有如下几个:
get(target, propKey, receiver)//拦截对象属性的读取,比如proxy.foo和proxy['foo']。
set(target, propKey, value, receiver)//拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
has(target, propKey) //当使用in判断属性是否在proxy代理对象里面时,会触发has
deleteProperty(target, propKey)//当使用delete去删除对象里面的属性的时候,拦截delete proxy[propKey]的操作,返回一个布尔值。
ownKeys(target)
//拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,
//返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
getOwnPropertyDescriptor(target, propKey)//拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
defineProperty(target, propKey, propDesc)
//拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
preventExtensions(target)//拦截Object.preventExtensions(proxy),返回一个布尔值。
getPrototypeOf(target)//拦截Object.getPrototypeOf(proxy),返回一个对象。
isExtensible(target)//拦截Object.isExtensible(proxy),返回一个布尔值。
setPrototypeOf(target, proto)
//拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
apply(target, object, args)
//拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
construct(target, args)//拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
traps | description |
---|---|
get | 获取某个key 值 |
set | 设置某个key 值 |
has | 使用in 操作符判断某个key 是否存在 |
apply | 函数调用,仅在代理对象为function 时有效 |
ownKeys | 获取目标对象所有的key |
construct | 函数通过实例化调用,仅在代理对象为function 时有效 |
isExtensible | 判断对象是否可扩展,Object.isExtensible 的代理 |
deleteProperty | 删除一个property |
defineProperty | 定义一个新的property |
getPrototypeOf | 获取原型对象 |
setPrototypeOf | 设置原型对象 |
preventExtensions | 设置对象为不可扩展 |
getOwnPropertyDescriptor | 获取一个自有属性 (不会去原型链查找) 的属性描述 |
每种方法的示例用法如下:一篇彻底理解Proxy
每个方法的参数可以参考MDN:handler_对象的方法
注意每个方法的返回值。比如set返回true表示成功。
Proxy 对象兼容性
Reflect 对象
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handlers 的方法相同。可简化的创建 Proxy
。
Reflect 对象兼容性
以前的内部方法,比如[[Get]]
,[[Set]]
等等都只是规范,不能直接调用。
Reflect
对象使调用这些内部方法成为可能。它的方法是内部方法的最小包装。
这是 Reflect
执行相同操作和调用的示例:
对于每个可被 **Proxy
** 捕获的内部方法,Reflect
** 都有一个对应的方法,其名称和参数与 **Proxy
** 钩子相同。**
因此,我们可以用 Reflect
来将操作转发到原始对象。
就是说,一切都很简单:如果钩子想要将调用转发给对象,则只需使用相同的参数调用 Reflect.<method>
就足够了。
let user = {
name: "John",
};
user = new Proxy(user, {
get(target, prop, receiver) {
alert(`GET ${prop}`);
return Reflect.get(target, prop, receiver); // (1)
},
set(target, prop, val, receiver) {
alert(`SET ${prop}=${val}`);
return Reflect.set(target, prop, val, receiver); // (2)
}
});
let name = user.name; // shows "GET name"
user.name = "Pete"; // shows "SET name=Pete"
参考连接:Proxy 和 Reflect
Reflect的每个方法使用示例:程序员不得不会的Reflect,有多少人不知道?
可取消的 Proxy
假设我们有一个资源,并且想随时关闭对该资源的访问。
我们可以做的是将其包装成可撤销的代理,而没有任何钩子。这样的代理会将操作转发给对象,我们可以随时将其禁用。
let {proxy, revoke} = Proxy.revocable(target, handler)
该调用返回一个带有 proxy
和 revoke
函数的对象以将其禁用。
这是一个例子:
let object = {
data: "Valuable data"
};
let {proxy, revoke} = Proxy.revocable(object, {});
// proxy 正常工作
alert(proxy.data); // Valuable data
// 之后某处调用
revoke();
// proxy 不再工作(已吊销)
alert(proxy.data); // Error
(完)