The trap of a Proxy
ECMAScript 6 introduced the Proxy object, which provides metaprogramming capabilities to JavaScript, allowing developers to intercept and customize fundamental operations on objects. The core of Proxy lies in its traps, which define how the proxy object intercepts underlying operations.
Basic Concepts of Proxy
The Proxy object is used to create a proxy for an object, enabling the interception and customization of fundamental operations. The Proxy constructor takes two parameters: the target object and the handler object. The handler object contains a set of trap methods that define the proxy's behavior.
const target = {};
const handler = {
get(target, prop, receiver) {
return Reflect.get(...arguments);
}
};
const proxy = new Proxy(target, handler);
Common Traps
get Trap
The get
trap intercepts property read operations. It is called when accessing a property of the proxy object.
const handler = {
get(target, prop) {
if (prop in target) {
return target[prop];
}
return `Property ${prop} does not exist`;
}
};
const proxy = new Proxy({}, handler);
console.log(proxy.name); // "Property name does not exist"
set Trap
The set
trap intercepts property assignment operations. It is called when assigning a value to a property of the proxy object.
const validator = {
set(target, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('Age must be an integer');
}
if (value < 0) {
throw new RangeError('Age must be positive');
}
}
target[prop] = value;
return true;
}
};
const person = new Proxy({}, validator);
person.age = 30; // Success
person.age = 'young'; // Throws TypeError
apply Trap
The apply
trap intercepts function calls. It is triggered when the proxy object is invoked as a function.
const handler = {
apply(target, thisArg, argumentsList) {
console.log(`Calling function with args: ${argumentsList}`);
return target(...argumentsList) * 2;
}
};
function sum(a, b) {
return a + b;
}
const proxy = new Proxy(sum, handler);
console.log(proxy(1, 2)); // Output: Calling function with args: 1,2 followed by 6
construct Trap
The construct
trap intercepts the new
operator. It is triggered when the proxy object is used as a constructor.
const handler = {
construct(target, args, newTarget) {
console.log(`Constructing with args: ${args}`);
return new target(...args);
}
};
class Person {
constructor(name) {
this.name = name;
}
}
const PersonProxy = new Proxy(Person, handler);
const p = new PersonProxy('Alice'); // Output: Constructing with args: Alice
Other Traps
has Trap
The has
trap intercepts the in
operator. It is called when checking for property existence.
const handler = {
has(target, prop) {
if (prop.startsWith('_')) {
return false;
}
return prop in target;
}
};
const obj = { _secret: 'foo', public: 'bar' };
const proxy = new Proxy(obj, handler);
console.log('_secret' in proxy); // false
console.log('public' in proxy); // true
deleteProperty Trap
The deleteProperty
trap intercepts the delete
operator. It is called when deleting a property.
const handler = {
deleteProperty(target, prop) {
if (prop.startsWith('_')) {
throw new Error(`Cannot delete private "${prop}" property`);
}
delete target[prop];
return true;
}
};
const obj = { _id: 123, name: 'Test' };
const proxy = new Proxy(obj, handler);
delete proxy.name; // Success
delete proxy._id; // Throws error
ownKeys Trap
The ownKeys
trap intercepts operations like Object.keys()
and Object.getOwnPropertyNames()
.
const handler = {
ownKeys(target) {
return Reflect.ownKeys(target).filter(key => !key.startsWith('_'));
}
};
const obj = { _id: 1, name: 'John', _password: 'secret' };
const proxy = new Proxy(obj, handler);
console.log(Object.keys(proxy)); // ["name"]
Advanced Usage
Revocable Proxy
The Proxy.revocable()
method creates a revocable Proxy object. Once the revoke
function is called, the proxy becomes unusable.
const { proxy, revoke } = Proxy.revocable({}, {
get(target, prop) {
return `Intercepted: ${prop}`;
}
});
console.log(proxy.foo); // "Intercepted: foo"
revoke();
console.log(proxy.foo); // TypeError: Cannot perform 'get' on a proxy that has been revoked
Chained Proxies
Multiple layers of proxies can be created, each handling different interception logic.
const firstHandler = {
get(target, prop) {
console.log(`First handler: ${prop}`);
return Reflect.get(target, prop);
}
};
const secondHandler = {
get(target, prop) {
console.log(`Second handler: ${prop}`);
return Reflect.get(target, prop);
}
};
const target = { message: 'Hello' };
const firstProxy = new Proxy(target, firstHandler);
const secondProxy = new Proxy(firstProxy, secondHandler);
console.log(secondProxy.message);
// Output:
// Second handler: message
// First handler: message
// Hello
Practical Applications
Data Validation
Proxy can be used to implement complex data validation logic.
const validator = {
set(target, prop, value) {
if (prop === 'email') {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
throw new Error('Invalid email format');
}
}
target[prop] = value;
return true;
}
};
const user = new Proxy({}, validator);
user.email = 'test@example.com'; // Success
user.email = 'invalid-email'; // Throws error
Performance Monitoring
Proxy can be used to monitor property access and modifications.
function createMonitoredObject(target) {
const accesses = new Set();
const modifications = new Set();
const handler = {
get(target, prop) {
accesses.add(prop);
console.log(`Property accessed: ${prop}`);
return Reflect.get(target, prop);
},
set(target, prop, value) {
modifications.add(prop);
console.log(`Property modified: ${prop}`);
return Reflect.set(target, prop, value);
}
};
const proxy = new Proxy(target, handler);
return {
proxy,
getAccesses: () => [...accesses],
getModifications: () => [...modifications]
};
}
const { proxy, getAccesses } = createMonitoredObject({ x: 1, y: 2 });
proxy.x; // Property accessed: x
proxy.y = 3; // Property modified: y
console.log(getAccesses()); // ["x"]
Implementing Negative Array Indices
Proxy can extend JavaScript syntax to implement negative array indices, similar to Python.
function createNegativeArray(array) {
return new Proxy(array, {
get(target, prop, receiver) {
if (typeof prop === 'string' && /^-?\d+$/.test(prop)) {
const index = parseInt(prop, 10);
if (index < 0) {
prop = target.length + index;
}
}
return Reflect.get(target, prop, receiver);
}
});
}
const array = createNegativeArray(['a', 'b', 'c', 'd']);
console.log(array[-1]); // "d"
console.log(array[-2]); // "c"
Considerations
Performance
Proxy operations are slower than direct object access. They should be used cautiously in performance-critical paths. For simple use cases, regular getters/setters may be more appropriate.
Non-proxyable Operations
Certain operations cannot be intercepted by Proxy, such as the instanceof
operator, typeof
operator, etc.
Target Object Immutability
Proxy does not modify the target object itself. All intercepted operations occur on the proxy object. Direct operations on the target object will not trigger any traps.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Proxy基本创建方式
下一篇:Reflect对象的方法