Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
基本用法
1
| const proxy = new Proxy(target, handler);
|
- target: 目标对象
- handler: 一个通常以函数作为属性的对象,用来定制拦截行为,各属性中的函数分别定义了在执行各种操作时代理 p 的行为;
举个例子
1 2 3 4 5 6 7 8 9 10 11 12
| const target = {}; const handler = { get(target, propKey, receiver) { console.log("get " + propKey); return 123; }, }; const proxy = new Proxy(target, handler);
console.log(proxy.foo);
|
上面代码对 target 对象架设了一层拦截,重定义了属性的读取(get)行为。
handler 对象的方法
handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。
所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。
get
get 方法用于拦截对象的读取属性操作,可以接受三个参数:
- target: 目标对象
- property: 被获取的属性名
- receiver: proxy 实例本身(严格地说,是操作行为所针对的对象)可选。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const target = { name: "张三", }; const handler = { get(target, propKey, receiver) { if (propKey in target) { return target[propKey]; } else { throw new ReferenceError('Prop name "' + propKey + '" does not exist.'); } }, }; const proxy = new Proxy(target, handler);
|
get 方法会拦截目标对象的以下操作:
- 访问属性: proxy[‘name’]和 proxy.name
- 访问原型链上的属性: Object.create(proxy)[name]
- Reflect.get()
1 2 3 4 5 6 7 8 9 10
| proxy["name"];
var obj = Object.create(proxy); obj.name;
Reflect.get(proxy, "name"); Reflect.get(proxy, "age");
|
set
set 方法是用来拦截某个属性的赋值操作,可以接受四个参数:
- target: 目标对象
- property: 被获取的属性名
- value: 新属性值
- receiver: 通常是 proxy 实例本身,可选。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| var target = {}; var handler = { set(target, propKey, value) { target[propKey] = value; console.log("property set: " + propKey + " = " + value); return true; }, }; var proxy = new Proxy(target, handler); console.log("a" in proxy);
proxy.a = 10; console.log("a" in proxy); console.log(proxy.a);
|
set 方法会拦截目标对象的以下操作:
- 指定属性值:proxy[foo] = bar 和 proxy.foo = bar
- 指定继承者的属性值:Object.create(proxy)[foo] = bar
- Reflect.set()
1 2 3 4 5 6
| var obj = Object.create(proxy); obj.b = 20;
Reflect.set(proxy, "age", 18);
|
apply
apply() 方法用于拦截函数的调用,call 和 apply 操作,可以接受三个参数:
- target: 目标对象(函数)。
- thisArg: 被调用时的上下文对象。
- argumentsList: 被调用时的参数数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function sum(x, y) { return x + y; } const handler = { apply(target, ctx, args) { console.log(`Calculate sum: ${args}`); return target(args[0], args[1]) * 10; }, }; const proxy = new Proxy(sum, handler); sum(1, 2); proxy(1, 2);
|
apply 方法会拦截目标对象的以下操作:
- proxy(…args)
- Function.prototype.apply() 和 Function.prototype.call()
- Reflect.apply()
1 2 3 4 5 6 7 8
| proxy.apply(null, [1, 2]); proxy.call(null, 1, 2);
Reflect.apply(proxy, null, [1, 2]);
|
上面的代码结果都是一样的。
has
has()方法是针对 in 操作符的代理方法,即判断对象是否具有某个属性时,has()方法可以接受两个参数:
- target: 目标对象。
- prop: 需要查询的属性名。
1 2 3 4 5 6 7 8 9 10 11 12
| var handler = { has(target, key) { if (key[0] === "_") { return false; } return key in target; }, }; var target = { _prop: "foo", prop: "foo" }; var proxy = new Proxy(target, handler); "_prop" in proxy; "prop" in proxy;
|
has 方法会拦截目标对象的以下操作:
- 属性查询: foo in proxy
- 继承属性查询: foo in Object.create(proxy)
- with 检查: with(proxy) { (foo); }
- Reflect.has()
1 2 3 4 5 6 7 8 9 10 11
| var obj = Object.create(proxy); "_prop" in proxy; "prop" in obj;
with (proxy) { console.log(prop); console.log(_prop); }
Reflect.has(proxy, "prop"); Reflect.has(proxy, "_prop");
|
construct
construct 方法用于拦截 new 操作符. 为了使 new 操作符在生成的 Proxy 对象上生效,用于初始化代理的目标对象自身必须具有[[Construct]]内部方法(即 new target 必须是有效的)。
construct 方法可以接受三个参数。
- target:目标对象。
- args:构造函数的参数数组。
- newTarget:创造实例对象时,new 命令作用的构造函数。
construct 方法可以拦截以下操作
- new proxy(…args)
- Reflect.construct()
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const p = new Proxy(function () {}, { construct: function (target, args) { console.log("called: " + args.join(", ")); return { value: args[0] * 10 }; }, });
new p(1).value;
Reflect.construct(p, [1]).value;
|
deleteProperty
deleteProperty 方法用于拦截 delete 操作,如果这个方法抛出错误或者返回 false,当前属性就无法被 delete 命令删除。
deleteProperty 方法接受两个个参数:
- target: 删除属性的目标对象。
- propertyKey: 需要删除的属性的名称。
该方法拦截以下操作:
- 删除属性: delete proxy[foo] 和 delete proxy.foo
- Reflect.deleteProperty()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| var target = { _prop: "_foo", prop: "foo" }; var proxy = new Proxy(target, { deleteProperty: function (target, key) { if (key[0] === "_") { return false; } delete target[key]; return true; }, });
delete proxy._prop;
console.log(target);
Reflect.deleteProperty(proxy, "prop");
console.log(target);
|
defineProperty
defineProperty 方法用于拦截对对象的 Object.defineProperty() 操作。它接受三个参数:
- target: 目标对象。
- property: 待检索其描述的属性名。
- descriptor: 待定义或修改的属性的描述符。
1 2 3 4 5 6 7 8 9 10 11
| var handler = { defineProperty: function (target, prop, descriptor) { if (prop[0] === "_") { throw new Error(`Invalid attempt to define private "${prop}" property`); } target[prop] = dsc; return true; }, }; var target = {}; var proxy = new Proxy(target, handler);
|
该方法会拦截目标对象的以下操作 :
- proxy.property=’value’
- Object.defineProperty()
- Reflect.defineProperty()
1 2 3 4 5 6 7 8 9
| proxy._prop = "easily scared";
const desc = { configurable: true, enumerable: true, value: 10 }; Object.defineProperty(proxy, "_a", desc);
Reflect.defineProperty(proxy, "_b", desc);
|
注意:当调用 Object.defineProperty() 或者 Reflect.defineProperty(),传递给 defineProperty 的 descriptor 有一个限制 - 只有以下属性才有用,非标准的属性将会被无视 :
- enumerable
- configurable
- writable
- value
- get
- set
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var proxy = new Proxy( {}, { defineProperty(target, prop, descriptor) { console.log(descriptor); return Reflect.defineProperty(target, prop, descriptor); }, } );
Object.defineProperty(proxy, "name", { value: "proxy", type: "custom", }); proxy.name;
|
getOwnPropertyDescriptor
getOwnPropertyDescriptor()方法拦截 Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者 undefined。
该方法接受两个参数:
- target: 目标对象。
- prop: 属性名称。
可以拦截这些操作:
- Object.getOwnPropertyDescriptor()
- Reflect.getOwnPropertyDescriptor()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var handler = { getOwnPropertyDescriptor(target, key) { if (key[0] === "_") { return; } return Object.getOwnPropertyDescriptor(target, key); }, };
var target = { _prop: "_foo", prop: "foo" }; var proxy = new Proxy(target, handler); Object.getOwnPropertyDescriptor(proxy, "name");
Object.getOwnPropertyDescriptor(proxy, "_prop");
Object.getOwnPropertyDescriptor(proxy, "prop");
|
getPrototypeOf
getPrototypeOf 是一个代理(Proxy)方法,当读取代理对象的原型时,该方法就会被调用。
它的参数只有一个:
1 2 3 4 5 6 7
| const obj = {}; var handler = { getPrototypeOf(target) { return Array.prototype; }, }; var proxy = new Proxy(obj, handler);
|
该方法会拦截以下操作:
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- proto
- Object.prototype.isPrototypeOf()
- instanceof
1 2 3 4 5 6 7
| console.log( Object.getPrototypeOf(proxy) === Array.prototype, Reflect.getPrototypeOf(proxy) === Array.prototype, proxy.__proto__ === Array.prototype, Array.prototype.isPrototypeOf(proxy), proxy instanceof Array );
|
isExtensible
isExtensible() 方法用于拦截对对象的 Object.isExtensible(),同时 isExtensible 方法必须返回一个 Boolean 值或可转换成 Boolean 的值。
它的参数:
该方法会拦截目标对象的以下操作:
- Object.isExtensible()
- Reflect.isExtensible()
1 2 3 4 5 6 7 8 9 10 11 12
| var proxy = new Proxy( {}, { isExtensible: function (target) { console.log("called"); return true; }, } );
Object.isExtensible(proxy); Reflect.isExtensible(proxy);
|
注意:Object.isExtensible(proxy) 必须同 Object.isExtensible(target)返回相同值。
1 2 3 4 5 6 7 8 9 10 11 12
| var empty = {}; var proxy = new Proxy(empty, { isExtensible: function (target) { return false; }, });
Object.isExtensible(proxy);
Object.preventExtensions(empty);
Object.isExtensible(proxy);
|
所以 Object.isExtensible(proxy)和 Object.isExtensible(target)的返回值要么都是 true,要么都是 false。
ownKeys
ownKeys()方法用来拦截对象自身属性的读取操作。可接受参数:
可以拦截以下操作:
- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
- Reflect.ownKeys()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| let target = { a: 1, b: 2, c: 3, [Symbol("d")]: 4, }; var proxy = new Proxy(target, { ownKeys: function (target) { console.log("called"); return Object.getOwnPropertyNames(target); }, });
Object.getOwnPropertyNames(proxy);
var proxy2 = new Proxy(target, { ownKeys: function (target) { console.log("called"); return Object.getOwnPropertySymbols(target); }, }); Object.getOwnPropertySymbols(proxy2);
var proxy3 = new Proxy(target, { ownKeys: function (target) { console.log("called"); return Object.keys(target); }, }); Object.keys(proxy3);
var proxy4 = new Proxy(target, { ownKeys: function (target) { console.log("called"); return Object.keys(target); }, }); Reflect.ownKeys(proxy4);
|
preventExtensions
preventExtensions() 方法用于设置对 Object.preventExtensions()的拦截。该方法必须返回一个布尔值,否则会被自动转为布尔值。可接受参数:
可以拦截以下操作:
- Object.preventExtensions()
- Reflect.preventExtensions()
这个方法有一个限制,只有目标对象不可扩展时(即 Object.isExtensible(proxy)为 false),proxy.preventExtensions 才能返回 true,否则会报错。
错误示例:
1 2 3 4 5 6 7 8 9 10 11
| var proxy = new Proxy( {}, { preventExtensions: function (target) { console.log("called"); return true; }, } );
Object.preventExtensions(proxy);
|
正确示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| var proxy = new Proxy( {}, { preventExtensions: function (target) { console.log("called"); Object.preventExtensions(target); return true; }, } );
Object.preventExtensions(proxy); console.log(Object.isExtensible(proxy));
|
setPrototypeOf
setPrototypeOf()方法主要用来拦截 Object.setPrototypeOf()方法。可接受两个参数:
- target: 目标对象
- prototype: 对象新原型或为 null.
可以拦截以下操作:
- Object.setPrototypeOf()
- Reflect.setPrototypeOf()
1 2 3 4 5 6 7 8 9 10 11 12
| var handler = { setPrototypeOf(target, newProto) { throw new Error("custom error"); }, };
var newProto = {}, target = {};
var p1 = new Proxy(target, handler); Object.setPrototypeOf(p1, newProto); Reflect.setPrototypeOf(p1, newProto);
|
上面代码中,只要修改 target 的原型对象,就会报错。
注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(non-extensible),setPrototypeOf()方法不得改变目标对象的原型。
Proxy.revocable()
创建一个可撤销的 Proxy 对象。
1 2 3 4 5 6 7 8 9 10
| let target = {}; let handler = {};
let { proxy, revoke } = Proxy.revocable(target, handler);
proxy.foo = 123; proxy.foo;
revoke(); proxy.foo;
|
Proxy.revocable()方法返回一个对象,该对象的 proxy 属性是 Proxy 实例,revoke 属性是一个函数,可以取消 Proxy 实例。上面代码中,当执行 revoke 函数之后,再访问 Proxy 实例,就会抛出一个错误。
Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
参考文献