x-note
Search…
ECMAScript 6 Proxy
Proxy主要用于定义基本操作的自定义行为,包括属性查找、赋值、枚举、函数调用等。
Proxy(target, handler)构造函数接收两个参数,target为被代理的对象,handler为处理器对象,用来自定义代理对象的各种操作。目前,一共有 13 种可代理操作:

get(target, propery, receiver)

在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
1
const target = {
2
name: 'aaa',
3
};
4
const targetProxy = new Proxy(target, {
5
// 在读取代理对象的某个属性时触发该操作
6
get(target, property, receiver) {
7
return target[property] || null;
8
}
9
});
10
11
targetProxy.name === target.name; // true
12
targetProxy.x === null; // true
Copied!
通过proxyget拦截可以轻松实现 readOnly 和单例模式。

set(target, propKey, value, receiver)

在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1时。
1
const proxy = new Proxy({}, {
2
set(obj, property, value) {
3
if (property === 'age') {
4
if (!Number.isInteger(value)) {
5
throw new TypeError('The age is not an integer');
6
}
7
if (value > 150 || value < 0) {
8
throw new RangeError('Wooo. Are you kinding me?');
9
}
10
} else if (property.startsWith('_')) {
11
throw new Error('Can not write property which start with "_"')
12
}
13
// 对于满足条件的 age 属性以及其他属性,直接保存
14
obj[prop] = value;
15
return true;
16
}
17
});
18
19
proxy.age; // undefined
20
proxy.age = 24;
21
proxy.age; // 24
22
proxy.age = '24' // TypeError: The age is not an integer
23
proxy.age = -1 // RangeError: Wooo. Are you kinding me?
24
proxy._sex = 'male'; // Error: Can not write property which start with "_"
Copied!
可以通过设置代理对象的set处理器轻松的根据需求对对象的写入进行控制。

deleteProperty(target, property)

在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo时。
1
const proxy = new Proxy({
2
_a: 1,
3
a: 2,
4
}, {
5
deleteProperty(target, property) {
6
if (property.startsWith('_')) {
7
throw Error('Can not delete property which start with "_"')
8
}
9
return delete target[property];
10
}
11
});
12
13
delete proxy.a;
14
proxy.a; // undefined
15
delete proxy._a; // Error: Can not delete property which start with "_"
Copied!

defineProperty(target, property, descriptor)

在定义代理对象某个属性时的属性描述时触发该操作,如Object.defineProperty()Object.defineProperties()
  • 如果目标对象不可扩展, 将不能添加属性;
  • 不能添加或者修改一个属性为不可配置的,如果它不作为一个目标对象的不可配置的属性存在的话;
  • 如果目标对象存在一个对应的可配置属性,这个属性可能不会是不可配置的;
  • 如果一个属性在目标对象中存在对应的属性,那么Object.defineProperty(target, prop, descriptor)将不会抛出异常;
  • 在严格模式下,false作为handler.defineProperty方法的返回值的话将会抛出 TypeError
1
const proxy = new Proxy({}, {
2
defineProperty(target, prop, descriptor) {
3
console.log('called: ' + prop);
4
return Object.defineProperty(target, prop, descriptor);
5
}
6
});
7
8
Object.defineProperty(proxy, 'a', {
9
configurable: false,
10
enumerable: true,
11
value: 10,
12
});
13
14
Object.defineProperty(proxy, 'a', {
15
configurable: true,
16
}); // TypeError: Cannot redefine property: a
Copied!

has(target, property)

在判断代理对象是否拥有某个属性时触发该操作,如in运算符。
  • 如果目标对象的某一属性本身不可被配置,则该属性不能够被代理隐藏
  • 如果目标对象为不可扩展对象,则该对象的属性不能够被代理隐藏
1
const proxy = new Proxy({
2
a: 1,
3
_a: 2,
4
}, {
5
has(target, prop) {
6
if (prop.startsWith('_')) {
7
return false;
8
}
9
return prop in target;
10
}
11
});
12
console.log('a' in proxy); // true
13
console.log('_a' in proxy); // false
14
15
const obj = { a: 10 };
16
Object.preventExtensions(obj);
17
const proxy2 = new Proxy(obj, {
18
has(target, prop) {
19
return false;
20
}
21
});
22
console.log('a' in proxy2);
23
// TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible
Copied!
has拦截对for...in循环不生效。
1
const obj = { x: 1, y: 2 };
2
const proxy = new Proxy(obj, {
3
has(target, prop) {
4
console.log(prop);
5
return false;
6
}
7
});
8
9
for (let b in proxy) {
10
console.log(proxy[b]);
11
}
12
// 1
13
// 2
Copied!

ownKeys(target)

在获取代理对象的所有属性键时触发该操作,如在 Object.getOwnPropertyNames(obj)时。
  • 结果必须是一个数组;
  • 数组的元素类型只能是StringSymbol
  • 结果列表必须包含目标对象的所有不可配置(non-configurable )、自有(own)属性的key;
  • 如果目标对象不可扩展,那么结果列表必须包含目标对象的所有自有(own)属性的key,不能有其它值。
1
const obj = {};
2
Object.defineProperty(obj, 'a', {
3
configurable: false,
4
enumerable: true,
5
value: 10
6
});
7
8
const p = new Proxy(obj, {
9
ownKeys(target) {
10
return ['a', Symbol.for('b'), 'c'];
11
}
12
});
13
console.log(Object.getOwnPropertyNames(p)); // [ 'a', 'c' ]
14
15
const p2 = new Proxy(obj, {
16
ownKeys(target) {
17
return ['a', 123, 12.5, true, false, undefined, null, {}, []];
18
}
19
});
20
console.log(Object.getOwnPropertyNames(p2)); // TypeError: XXX is not a valid property name
21
22
const p3 = new Proxy(obj, {
23
ownKeys(target) {
24
return ['b', 'c'];
25
}
26
});
27
console.log(Object.getOwnPropertyNames(p3));
28
// TypeError: 'ownKeys' on proxy: trap result did not include 'a'
29
30
Object.preventExtensions(obj);
31
const p4 = new Proxy(obj, {
32
ownKeys(target) {
33
return ['a', 'b'];
34
}
35
});
36
console.log(Object.getOwnPropertyNames(p4));
37
// TypeError: 'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible
Copied!

getOwnPropertyDescriptor(target, property)

在获取代理对象某个属性的属性描述时触发该操作,如:Object.getOwnPropertyDescriptor(obj, prop)
  • 必须返回一个objectundefined
  • 如果属性作为目标对象的不可配置的属性存在,则该属性无法报告为不存在
  • 如果属性作为目标对象的属性存在,并且目标对象不可扩展,则该属性无法报告为不存在
  • 如果属性不存在作为目标对象的属性,并且目标对象不可扩展,则不能将其报告为存在
  • 属性不能被报告为不可配置,如果它不作为目标对象的自身属性存在,或者作为目标对象的可配置的属性存在
  • Object.getOwnPropertyDescriptor(target)的结果可以使用Object.defineProperty应用于目标对象,也不会抛出异常
1
const obj = { a: 20 };
2
Object.defineProperty(obj, 'b', {
3
configurable: false,
4
enumerable: true,
5
value: 20
6
});
7
8
9
var p = new Proxy(obj, {
10
getOwnPropertyDescriptor(target, prop) {
11
return { configurable: true, enumerable: true, value: 10 };
12
}
13
});
14
console.log(Object.getOwnPropertyDescriptor(p, 'a').value);
15
16
17
var p2 = new Proxy(obj, {
18
getOwnPropertyDescriptor(target, prop) {
19
return [];
20
}
21
});
22
console.log(Object.getOwnPropertyDescriptor(p2, 'b'));
23
// TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property 'a' which is either non-existant or configurable in the proxy target
24
25
var p3 = new Proxy(obj, {
26
getOwnPropertyDescriptor(target, prop) {
27
return undefined;
28
}
29
});
30
console.log(Object.getOwnPropertyDescriptor(p3, 'b'));
31
// TypeError: 'getOwnPropertyDescriptor' on proxy: trap returned undefined for property 'a' which is non-configurable in the proxy target
Copied!

apply(target, context, args)

在调用一个目标对象为函数的代理对象时触发该操作,比如 proxy()proxy.call()proxy.apply()
target必须可被调用
1
function target(...args) {
2
return args.reduce((sum, item) => sum + item, 0);
3
}
4
5
const proxy = new Proxy(target, {
6
apply(target, context, args) {
7
console.log(context);
8
return target(...args) - 1;
9
}
10
});
11
12
proxy(1, 2, 3); // undefined 5
13
proxy.call(null, 1, 2, 3); // null 5
14
proxy.apply(window, [1, 2, 3]); // window 5
Copied!

construct (target, args, newTarget)

target必须是一个合法的 constructor ,并且必须返回一个对象
1
const proxy = new Proxy(function () {}, {
2
construct: function(target, args, newTarget) {
3
return { value: args[0] * 10 };
4
}
5
});
6
(new proxy(1)).value; // 10
7
8
const proxy2 = new Proxy(function () {}, {
9
construct: function(target, args, newTarget) {}
10
});
11
new proxy2(1); // TypeError: 'construct' on proxy: trap returned non-object ('undefined')
12
13
const proxy3 = new Proxy({}, {
14
construct: function(target, args, newTarget) {
15
return { value: args[0] * 10 };
16
}
17
});
18
new proxy3(1); // TypeError: proxy3 is not a constructor
Copied!

getPrototypeOf(proxy)

在读取代理对象的原型时触发该操作,比如在执行Object.getPrototypeOf(proxy)时。
1
const proxy = new Proxy({}, {
2
getPrototypeOf(target) {
3
return Array.prototype;
4
}
5
});
6
7
Object.getPrototypeOf(proxy) === Array.prototype; // true
8
proxy.__proto__ === Array.prototype; // true
9
proxy.isPrototypeOf(Array.prototype); // true
10
console.log(proxy instanceof Array); // true
Copied!
当同时触发处理器getgetPrototypeOf操作时,get的优先级更高。
1
const proxy = new Proxy({}, {
2
getPrototypeOf(target) {
3
return Array.prototype;
4
},
5
get(target, property) {
6
return null;
7
}
8
});
9
10
Object.getPrototypeOf(proxy) === Array.prototype; // true
11
proxy.__proto__ === Array.prototype; // false
12
proxy.isPrototypeOf(Array.prototype); // TypeError: proxy.isPrototypeOf is not a function
13
console.log(proxy instanceof Array); // true
Copied!

setPrototypeOf(target, proto)

在设置代理对象的原型时触发该操作,如:Object.setPrototypeOf(proxy)。如果返回false会抛出个TypeError异常。
如果target不可扩展,原型参数必须与Object.getPrototypeOf()的值相同。
1
const proxy = new Proxy(function () {}, {
2
setPrototypeOf(target, proto) {
3
return false;
4
}
5
});
6
Object.setPrototypeOf(proxy, {}); // TypeError: 'setPrototypeOf' on proxy: trap returned falsish
7
8
const target = {};
9
Object.preventExtensions(target);
10
const proxy2 = new Proxy(target, {
11
setPrototypeOf(target, proto) {
12
return true;
13
}
14
});
15
Object.setPrototypeOf(proxy2, {});
16
// TypeError: 'setPrototypeOf' on proxy: trap returned truish for setting a new prototype on the non-extensible proxy target
Copied!

isExtensible(target)

在判断一个代理对象是否是可扩展时触发该操作,如:Object.isExetensible(proxy)时。
Object.isExtensible(proxy) 必须返回与Object.isExtensible(target)相同的值。
1
const p = new Proxy({}, {
2
isExtensible(target) {
3
return true;
4
}
5
});
6
Object.isExtensible(p); // true
7
8
const obj = {};
9
Object.preventExtensions(obj);
10
const p1 = new Proxy(obj, {
11
isExtensible(target) {
12
return true;
13
}
14
});
15
const p2 = new Proxy(obj, {
16
isExtensible(target) {
17
return false;
18
}
19
});
20
Object.isExtensible(p1); // TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'false')
21
Object.isExtensible(p2); // false
Copied!

preventExtensions(target)

在让一个代理对象不可扩展时触发该操作,比如在执行Object.preventExtensions(proxy)时。如果返回false会抛出个TypeError异常。
如果Object.isExtensible(proxy)falseObject.preventExtensions(proxy)只能返回true
1
const p = new Proxy({}, {
2
preventExtensions(target) {
3
Object.preventExtensions(target);
4
return false;
5
}
6
});
7
8
Object.preventExtensions(p) // TypeError: 'preventExtensions' on proxy: trap returned falsish
Copied!