A JavaScript Proxy is an object that wraps another object (the target) and intercepts fundamental operations such as property access, assignment, enumeration, function invocation, etc. It uses a handler object with traps, which are functions that will be called when the corresponding operation is performed on the proxy.
// Example of a simple Proxy
const target = {
message1: "hello",
message2: "everyone"
};
const handler = {
get: function(target, prop, receiver) {
return `You accessed the property '${prop}' which has value: ${target[prop]}`;
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.message1);
// Output: You accessed the property 'message1' which has value: hello
The Reflection API in JavaScript provides a set of static methods that can be used to perform the same operations that Proxies can intercept. These methods are designed to mirror the behavior of normal JavaScript operations but in a more programmatic way. For example, Reflect.get()
can be used to get the value of a property on an object.
const obj = {
name: "John",
age: 30
};
const value = Reflect.get(obj, 'name');
console.log(value);
// Output: John
To create a Proxy, you need to use the Proxy
constructor. It takes two arguments: the target object and a handler object.
const target = {
num: 10
};
const handler = {
set: function(target, prop, value) {
if (typeof value === 'number') {
target[prop] = value;
} else {
console.log('Value must be a number.');
}
return true;
}
};
const proxy = new Proxy(target, handler);
proxy.num = 20;
console.log(proxy.num);
// Output: 20
proxy.num = 'abc';
// Output: Value must be a number.
The Reflection API methods can be used directly on objects. For example, Reflect.has()
can be used to check if an object has a certain property.
const myObj = {
city: "New York"
};
const hasProperty = Reflect.has(myObj, 'city');
console.log(hasProperty);
// Output: true
Proxies can be used to validate and sanitize input when setting object properties.
const user = {
age: null
};
const userHandler = {
set: function(target, prop, value) {
if (prop === 'age') {
if (typeof value === 'number' && value >= 0 && value <= 120) {
target[prop] = value;
} else {
console.log('Invalid age.');
}
} else {
target[prop] = value;
}
return true;
}
};
const userProxy = new Proxy(user, userHandler);
userProxy.age = 25;
console.log(userProxy.age);
// Output: 25
userProxy.age = 150;
// Output: Invalid age.
Proxies can be used to log every time a property is accessed or modified.
const data = {
price: 100
};
const loggingHandler = {
get: function(target, prop, receiver) {
console.log(`Getting property '${prop}'`);
return Reflect.get(target, prop, receiver);
},
set: function(target, prop, value, receiver) {
console.log(`Setting property '${prop}' to '${value}'`);
return Reflect.set(target, prop, value, receiver);
}
};
const dataProxy = new Proxy(data, loggingHandler);
dataProxy.price;
// Output: Getting property 'price'
dataProxy.price = 200;
// Output: Setting property 'price' to '200'
Proxies can be used to control access to certain properties of an object.
const secretObj = {
password: "abc123",
username: "user1"
};
const accessHandler = {
get: function(target, prop, receiver) {
if (prop === 'password') {
console.log('Access to password is restricted.');
return undefined;
}
return Reflect.get(target, prop, receiver);
}
};
const secretProxy = new Proxy(secretObj, accessHandler);
console.log(secretProxy.username);
// Output: user1
console.log(secretProxy.password);
// Output: Access to password is restricted.
// Output: undefined
Proxies can have a performance impact, especially if they are used extensively. Each time a proxied operation is performed, the corresponding trap in the handler is called, which adds some overhead. Therefore, it’s important to use Proxies only when necessary.
When using Proxies and the Reflection API, it’s important to handle errors properly. For example, if a Proxy trap throws an error, it can cause unexpected behavior in your application. You should use try - catch blocks when performing operations that might throw errors.
const target = {};
const handler = {
get: function(target, prop, receiver) {
if (prop === 'invalidProp') {
throw new Error('Invalid property access');
}
return Reflect.get(target, prop, receiver);
}
};
const proxy = new Proxy(target, handler);
try {
console.log(proxy.invalidProp);
} catch (error) {
console.log(error.message);
// Output: Invalid property access
}
JavaScript Proxies and the Reflection API are powerful tools for advanced meta - programming. They allow you to intercept and customize fundamental operations on objects, enabling you to implement features such as validation, logging, and access control. However, they should be used with caution due to potential performance implications. By understanding the fundamental concepts, usage methods, common practices, and best practices, you can effectively leverage these features to build more robust and flexible JavaScript applications.