Explain Proxy object.
The JavaScript `Proxy` object allows you to create an object that can be used in place of another object (the target) but can intercede operations performed on the target. It enables you to define custom behavior for fundamental operations like property lookup, assignment, enumeration, function invocation, and more.
What is a Proxy?
A Proxy object wraps another object, called the 'target'. It acts as an intermediary, allowing you to intercept and customize operations performed on the target object. This interception is done through a 'handler' object, which defines specific methods called 'traps'.
Key Concepts
Target Object
The object that the proxy virtualizes. It can be any object, including another proxy, a function, or even an empty object. The operations (like getting a property) that are not intercepted by the handler will be forwarded to the target object by default.
Handler Object
An object containing 'trap' methods. Each trap corresponds to a specific fundamental operation on the proxy. If a trap is not defined in the handler, the operation is forwarded to the target object.
Traps
Methods defined in the handler object that intercept fundamental operations. When an operation is performed on the proxy, if a corresponding trap exists in the handler, that trap method is executed instead of the default operation on the target. For example, the get trap intercepts property reads, and the set trap intercepts property assignments.
- get(target, property, receiver): Intercepts property reads.
- set(target, property, value, receiver): Intercepts property assignments.
- apply(target, thisArg, argumentsList): Intercepts function calls.
- construct(target, argumentsList, newTarget): Intercepts
newoperator calls. - has(target, property): Intercepts
inoperator. - deleteProperty(target, property): Intercepts
deleteoperator. - ownKeys(target): Intercepts
Object.keys(),Object.getOwnPropertyNames(),Object.getOwnPropertySymbols().
Syntax
const p = new Proxy(target, handler);
Example
Basic Property Access Trap
const target = {
message1: 'hello',
message2: 'world'
};
const handler = {
get(target, property, receiver) {
console.log(`Getting property '${String(property)}'`);
return property in target ? target[property] : `Property '${String(property)}' does not exist.`;
},
set(target, property, value, receiver) {
console.log(`Setting property '${String(property)}' to '${value}'`);
if (typeof value !== 'string') {
throw new TypeError('Value must be a string.');
}
target[property] = value;
return true;
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.message1); // Output: Getting property 'message1'
// hello
console.log(proxy.message3); // Output: Getting property 'message3'
// Property 'message3' does not exist.
proxy.message1 = 'hi'; // Output: Setting property 'message1' to 'hi'
console.log(target.message1); // Output: hi
try {
proxy.message4 = 123; // Output: Setting property 'message4' to '123'
// TypeError: Value must be a string.
} catch (e) {
console.error(e.message);
}
Validation Example
const validator = {
set(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value < 0 || value > 200) {
throw new RangeError('The age is out of range');
}
}
// The default behavior to store the value
obj[prop] = value;
return true;
}
};
const person = new Proxy({}, validator);
person.age = 35;
console.log(person.age); // 35
// person.age = 'young'; // Throws TypeError: The age is not an integer
// person.age = -1; // Throws RangeError: The age is out of range
Use Cases
- Validation: Intercepting property assignments to validate input (as shown in the example).
- Logging: Logging operations performed on an object (reads, writes, function calls).
- Access Control/Authorization: Preventing access to certain properties or methods based on permissions.
- Data Binding: Automatically updating UI components when an object's properties change.
- Memoization: Caching results of expensive function calls.
- Reactivity (e.g., Vue.js 3): Making objects reactive by intercepting property access and modification.
- Revocable Proxies: Creating proxies that can be revoked, making them unusable after a certain point.
- Debugging: Monitoring object interactions.
Limitations and Considerations
- Performance: Proxies can introduce a slight performance overhead compared to direct object operations, though often negligible for most applications.
- Complexity: Over-reliance on proxies for simple tasks can make code harder to understand and debug.
thiscontext: When using proxies with methods that rely onthis, ensure thethiscontext is correctly handled, often by explicitly binding or using thereceiverargument in traps.- Invariants: Several traps have 'invariants' that must be maintained (e.g., a non-configurable, non-writable property of the target must retain the same value when set via the proxy). Failing to respect these invariants will result in a
TypeError.