Proxy
Proxy对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义,如属性查找、赋值、枚举、函数调用等。
const p = new Proxy(target, handler)
target: 要使用Proxy包装的目标对象。可以是任何类型的对象,包括原生数组、函数,甚至是另一个代理。
handler:通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p的行为。
方法
Proxy方法
Proxy.revocable(target, handler) 创建一个可撤销的Proxy对象。
const revocable = Proxy.revocable({}, {
get(target, name) {
return `[[${name}]]`
}
});
// revocable返回对象 {proxy, revoke}
// proxy 表示新生成的代理对象本身;
// revoke 表示撤销方法,调用的时候不需要加任何参数
const proxy = revocable.proxy;
proxy.foo; // "[[foo]]"
revocable.revoke();
console.log(proxy.foo); // TypeError: Cannot perform 'get' on a proxy that has been revoked
proxy.foo = 1; // TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.foo; // TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
typeof proxy; // 'object', 因为typeof不属于可代理操作
handler对象的方法
handler对象是容纳一批特定属性的占位符对象,包含有Proxy各个捕获器。所有的捕获器是可选的,如果没有定义某个捕获器,就会保留源对象的默认行为。(handler对象方法篇幅较长,在文末附文)
示例
基础示例
const handler = {
get(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
无操作转发代理
const target = {};
const p = new Proxy(target, {});
p.a = 37;
console.log(target.a); // 37
验证
const validator = {
set(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not a integer!');
}
if (value > 200) {
throw new RangeError('The age invalid!');
}
}
obj[prop] = value;
return true;
}
}
const proxy = new Proxy({}, validator);
proxy.age = 100;
console.log(proxy.age); // 100;
proxy.age = "aa"; // TypeError: The age is not a integer!
proxy.age = 201; // RangeError: The age invalid!
扩展构造函数
function extend(sup, base) {
const descriptor = Object.getOwnPropertyDescriptor(
base.prototype, "constructor"
);
base.prototype = Object.create(sup.prototype);
const handler = {
construct(target, args) {
const obj = Object.create(base.prototype);
this.apply(target, obj, args);
return obj;
},
apply(target, that, args) {
sup.apply(that, args);
base.apply(that, args);
}
};
const proxy = new Proxy(base, handler);
descriptor.value = proxy;
Object.defineProperty(base.prototype, "constructor", descriptor);
return proxy;
}
const Person = function (name) {
this.name = name;
}
const Boy = extend(Person, function(name, age) {
this.age = age;
})
Boy.prototype.sex = "男";
const Peter = new Boy("Peter", 13);
Peter.sex; // "男"
Peter.name; // Peter
Peter.age; // 13
附:handler对象方法
handler.getPrototypeOf() Object.getPrototypeOf 方法的捕获器。
// 返回值必须是对象或者 `null`
const obj = {}
const p = new Proxy(obj, {
getPrototypeOf(target) {
return Array.prototype;
}
});
// 触发 `getPrototypeOf` 的五种方式
console.log(
Object.getPrototypeOf(p) === Array.prototype, // true
Reflect.getPrototypeOf(p) === Array.prototype, // true
p.__proto__ === Array.prototype, // true
Array.prototype.isPrototypeOf(p), // true
p instanceof Array // true
);
// 返回值不是对象或者 `null`,抛出异常
const obj = {}
const p = new Proxy(obj, {
getPrototypeOf(target) {
return 'foo';
}
});
Object.getPrototypeOf(p); // TypeError: 'getPrototypeOf' on proxy: trap returned neither object nor null
// 目标对象不可扩展,且 `getPrototypeOf()` 方法返回的原型不是目标对象本身的原型,抛出异常
const obj = Object.preventExtensions({});
const p = new Proxy(obj, {
getPrototypeOf(target) {
return {};
}
})
Object.getPrototypeOf(p); // TypeError: 'getPrototypeOf' on proxy: proxy target is non-extensible but the trap did not return its actual prototype
handler.setPrototypeOf(target, prototype) Object.setPrototypeOf 方法的捕获器
// target 被拦截目标对象
// prototype 对象新原型或为null
// 如果成功修改了[[prototype]],返回值为boolean值
const handler = {
setPrototypeOf(target, newProto) {
return false
}
}
const newProto = {}, target = {};
let p1 = new Proxy(target, handler);
Object.setPrototypeOf(p1, newProto); // TypeError: 'setPrototypeOf' on proxy: trap returned falsish for property 'undefined'
Reflect.setPrototypeOf(p1, newProto); // false
handler.isExtensible() Object.isExtensible 方法的捕获器
// 返回值必须是Boolean值或可转换成Boolean的值
const p = new Proxy({}, {
isExtensible(target) {
console.log('do');
return true;
}
})
Object.isExtensible(p); // do true
// Reflect.isExtensible(p); // do true
// Object.isExtensible(proxy) 必须同Object.isExtensbile(target)返回值相同。
// 即必须返回true或为true的值,返回false或为false的值都会报错
const p = new Proxy({}, {
isExtensible(target) {
return false;
}
})
Object.isExtensible(p);
Reflect.isExtensible(p);
//TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')
handler.preventExtensions() Object.preventExtensions 方法的捕获器
// 返回布尔值
const p = new Proxy({}, {
preventExtensions(target) {
console.log('do');
Object.preventExtensions(target);
return true;
}
})
Object.preventExtensions(p); // do Proxy{...}
Reflect.preventExtensions(p); // do true
// 对象是可扩展的,抛出异常
const p = new Proxy({}, {
preventExtensions(target) {
return true;
}
})
Object.preventExtensions(p);
Reflect.preventExtensions(p);
// TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible
const p = new Proxy({}, {
preventExtensions(target) {
return false;
}
})
Object.preventExtensions(p); // TypeError: 'preventExtensions' on proxy: trap returned falsish
Reflect.preventExtensions(p); // false
handler.getOwnPropertyDescriptor(target, prop) Object.getOwnPropertyDescriptor 方法的捕获器
// target 目标对象
// prop 返回属性名称的描述
// 返回值必须是object或undefined
const p = new Proxy({a: 20}, {
getOwnPropertyDescriptor(target, prop) {
console.log(`called: ${prop}`);
return {configurable: true, enumerable: true, value: 10}
}
});
Object.getOwnPropertyDescriptor(p, 'a').value;
Reflect.getOwnPropertyDescriptor(p, 'a').value;
// called: a 10
// 如果不变量被违反,则抛出异常
const obj = {a: 10};
Object.preventExtensions(obj);
const p = new Proxy(obj, {
getOwnPropertyDescriptor(target, prop) {
return undefined;
}
});
Object.getOwnPropertyDescriptor(p, 'a')
Reflect.getOwnPropertyDescriptor(p, 'a');
// ypeError: 'getOwnPropertyDescriptor' on proxy: trap returned undefined for property 'a' which exists in the non-extensible proxy target
handler.defineProperty(target, property, descriptor) Object.defineProperty 方法的捕获器
// target 目标对象
// property 待检索其属性的属性名
// descriptor 待定义或修改的属性的描述符
// 返回值为Boolean类型的值
const p = new Proxy({}, {
defineProperty(target, prop, descriptor) {
console.log(`called: ${prop}`);
return true;
}
});
const desc = {configurable: true, enumerable: true, value: 10};
Object.defineProperty(p, 'a', desc); // called: a Proxy{...}
Reflect.defineProperty(p, 'a', desc); // called: a true
p.a = 111 // called: a
// 如果违背不变量,则抛出异常
const desc = {configurable: false, enumerable: true, value: 10};
Object.defineProperty(p, 'a', desc);
Reflect.defineProperty(p, 'a', desc);
// TypeError: 'defineProperty' on proxy: trap returned truish for defining non-configurable property 'a' which is either non-existent or configurable in the proxy target
const p = new Proxy({}, {
defineProperty(target, prop, descriptor) {
console.log(`called: ${prop}`);
return false;
}
});
const desc = {configurable: true, enumerable: true, value: 10};
Object.defineProperty(p, 'a', desc);
// TypeError: 'defineProperty' on proxy: trap returned falsish for property 'a'
handler.has(target, prop) in 操作符的捕获器
// target 目标对象
// prop 需要检查是否存在的属性
// 返回Boolean值
const obj = {a: 10};
Object.preventExtensions(obj);
const p = new Proxy(obj, {
has(target, prop) {
console.log(`called: ${prop}`)
return true;
}
});
'a' in p; // called: a true
'a' in Object.create(p); // called: a true
with(p) {(a);} // called: a 10
Reflect.has(p, 'a') // called: a true
// 如果目标对象某一属性本身不可被配置,代理隐藏该属性,抛出异常
// 如果目标对象为不可扩展对象,代理隐藏该属性,抛出异常
const obj = {a: 10};
Object.preventExtensions(obj);
const p = new Proxy(obj, {
has(target, prop) {
return false;
}
});
'a' in p; // TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible
handler.get(target, property, receiver) 属性读取操作的捕获器
// target 目标对象
// property 被获取的属性名
// receiver Proxy或继承Proxy的对象
// 返回任意值
const p = new Proxy({}, {
get(target, property, receiver) {
console.log(`called: ${property}`);
return 10;
}
});
p.a; // called: a 10
p['a']; // called: a 10
Object.create(p)['a']; // called: a 10
Reflect.get(p, 'a'); // called: a 10
// 访问的目标属性不可写以及不可配置时,返回值与该目标属性的值不同,抛出异常
// 访问的目标属性没有配置访问方法时,即get方法是undefined的,返回值不为undefined,抛出异常
const obj = {};
Object.defineProperty(obj, "a", {
configurable: false,
enumerable: false,
value: 10,
writable: false
});
const p = new Proxy(obj, {
get(target, prop, receiver) {
return 20;
}
});
p.a; // TypeError: 'get' on proxy: property 'a' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '10' but got '20')
handler.set(target, property, value, receiver) 属性设置操作的捕获器
// target 目标对象
// property 将被设置的属性名或者Symbol
// value 新属性值
// receiver 最初被调用的对象。通常是Proxy本身,但handler的set方法也有可能出现在原型链上,或以其他方式被间接地调用
// 返回Boolean值
const p = new Proxy({}, {
set(target, prop, value, receiver) {
target[prop] = value;
console.log(`set: ${prop}=${value}`);
return true;
}
});
'a' in p; // false
p.a = 10; // set: a=10;
p['a'] = 10; // set: a=10;
Object.create(p)['a'] = 10; // set: a=10;
Reflect.set(p, 'a', 10); // set: a=10;
// 目标属性不可写及不可配置,值改变时,抛出异常
// 目标属性没有配置存储方法,即[[set]]属性为undefined,设置值时抛出异常
// set()返回false,抛出异常
const obj = {};
Object.defineProperty(obj, "a", {
configurable: false,
enumerable: false,
value: 10,
writable: false
});
const p = new Proxy(obj, {
set(target, prop, value, receiver) {
target[prop] = value;
return true;
}
});
p.a = 20; // TypeError: 'set' on proxy: trap returned truish for property 'a' which exists in the proxy target as a non-configurable and non-writable data property with a different value
handler.deleteProperty(target, property) delete 操作符的捕获器
// target 目标对象
// property 待删除的属性名
// 返回Boolean值
const p = new Proxy({}, {
deleteProperty(target, prop) {
console.log(`called: ${prop}`);
return true;
}
});
delete p.a; // called: a
delete p['a']; // called: a
// 目标对象的属性不可配置,删除该属性时抛出异常
const obj = {};
Object.defineProperty(obj, 'a', {
configurable: false,
writable: false,
value: 20
});
const p = new Proxy(obj, {
deleteProperty(target, prop) {
return true;
}
});
delete p.a; // TypeError: 'deleteProperty' on proxy: trap returned truish for property 'a' which is non-configurable in the proxy target
handler.ownKeys() Object.getOwnPropertyNames 方法和Object.getOwnPropertySymbols 方法的捕获器
// 返回枚举对象
const p = new Proxy({a: 1, b: 2, c: 3}, {
ownKeys(target) {
return ['a', 'd'];
}
});
Object.getOwnPropertyNames(p); // ['a', 'd']
Object.keys(p); // ['a']
Reflect.ownKeys(p); // ['a', 'd']
const p = new Proxy({[Symbol.for('k')]: 'ss'}, {
ownKeys(target) {
return [Symbol.for('k')];
}
});
Object.getOwnPropertySymbols(p); // [Symbol(k)]
// ownKeys 的结果必须是数组,否则抛出异常
// 返回的数组元素类型只能是String或Symbol,否则抛出异常
// 结果列表必须包含目标对象的所有不可配置、自有属性的key,否则抛出异常
// 目标对象不可扩展,结果列表必须包含目标对象的所有自有属性的key,不能有其他值,否则抛出异常
const obj = {};
Object.defineProperty(obj, 'a', {
configurable: false,
enumerable: true,
value: 10
});
const p = new Proxy(obj, {
ownKeys(target) {
return [1, 2, 3, true, false, null, undefined, {}, []] // TypeError: 1 is not a valid property name
// return ['d'] // TypeError: 'ownKeys' on proxy: trap result did not include 'a'
// return 'a' // TypeError: CreateListFromArrayLike called on non-object
}
});
Object.getOwnPropertyNames(p);
handler.apply(target, thisArg, argumentsList) 函数调用操作的捕获器
// target 目标对象
// thisArg 被调用时的上下文对象
// argumentsList 被调用时的参数数组
// 返回任何值
const p = new Proxy(() => {}, {
apply(target, thisArg, argumentsList) {
console.log(`called: ${argumentsList.join(', ')}`);
const [a1, a2, a3] = argumentsList;
return a1 + a2 + a3;
}
});
p(1, 2, 3); // called: 1, 2, 3 6
// target必须是一个函数对象
const p = new Proxy({}, {
apply(target, thisArg, argumentsList) {
console.log(`called: ${argumentsList.join(', ')}`);
const [a1, a2, a3] = argumentsList;
return a1 + a2 + a3;
}
});
p(1, 2, 3); // TypeError: p is not a function
handler.construct(target, argumentsList, newTarget) new 操作符的捕获器
// target 目标对象
// argumentsList constructor的参数列表
// newTarget 最初被调用的构造函数
// 返回一个对象
const p = new Proxy(function() {}, {
construct(target, argumentsList, newTarget) {
console.log(`called: ${argumentsList.join(', ')}`);
return {value: argumentsList[0] * 10}
}
})
new p(2).value; // called: 2 20
// 返回值必须是对象,否则抛出异常
// target必须具有有效的conctructor供new操作符调用,否则抛出异常
const p = new Proxy(() => {}, {
construct(target, argumentsList, newTarget) {
console.log(`called: ${argumentsList.join(', ')}`);
return {value: argumentsList[0] * 10}
}
})
new p(2).value; // TypeError: p is not a constructor
const p = new Proxy(function() {}, {
construct(target, argumentsList, newTarget) {
console.log(`called: ${argumentsList.join(', ')}`);
return 1
}
})
new p(2).value; // TypeError: 'construct' on proxy: trap returned non-object ('1')
参考资料:[MDN](Proxy - JavaScript | MDN (mozilla.org))