Proxy and Reflect Objects in JavaScript
Description
Proxy is a mechanism in JavaScript for creating object proxies, allowing you to intercept and customize fundamental operations on objects (such as property reading, assignment, function invocation, etc.). Reflect is a built-in object that provides a set of methods corresponding to Proxy interceptors, used to perform default behavior. The two are often used together to enable metaprogramming (e.g., data binding, validation).
Basic Concepts
-
Purpose of Proxy:
- Creates a proxy for an object, listening to operations on the object via interceptors (traps).
- For example, intercepting the read operation
obj.namecan return a custom value.
-
Purpose of Reflect:
- Provides static methods (e.g.,
Reflect.get(),Reflect.set()) that correspond one-to-one with Proxy interceptors. - Used within Proxy to invoke the object's default behavior, avoiding manual reimplementation of logic.
- Provides static methods (e.g.,
Creating Proxy and Interceptors
-
Creating a Proxy Object:
- Syntax:
new Proxy(target, handler) target: The target object to be proxied.handler: An object containing interceptors (e.g.,get,set).
Example:
const target = { name: "Alice" }; const handler = { get(obj, prop) { return prop in obj ? obj[prop] : "Default Value"; } }; const proxy = new Proxy(target, handler); console.log(proxy.name); // "Alice" console.log(proxy.age); // "Default Value" - Syntax:
-
Common Interceptors:
get(target, prop, receiver): Intercepts property reading.set(target, prop, value, receiver): Intercepts property assignment.has(target, prop): Intercepts theinoperator.deleteProperty(target, prop): Interceptsdeleteoperations.
Example (Property Assignment Validation):
const validator = { set(obj, prop, value) { if (prop === "age" && typeof value !== "number") { throw new TypeError("Age must be a number"); } obj[prop] = value; // Perform default assignment operation return true; // Indicates success } }; const person = new Proxy({}, validator); person.age = 30; // Normal person.age = "30"; // Throws error
Using Reflect in Conjunction
-
Why Reflect is Needed:
- Within Proxy interceptors, directly manipulating the target object (e.g.,
obj[prop] = value) may encounter issues (such as assignment failure when a property is read-only). - Reflect methods return Boolean values (e.g.,
Reflect.set()) or specific values (e.g.,Reflect.get()), making it easier to determine if an operation succeeded.
- Within Proxy interceptors, directly manipulating the target object (e.g.,
-
Modifying the Above Example:
const validator = { set(obj, prop, value) { if (prop === "age" && typeof value !== "number") { throw new TypeError("Age must be a number"); } return Reflect.set(obj, prop, value); // Invoke default behavior } };
Practical Application Scenarios
-
Data Binding and Reactivity:
- Listen for object changes via Proxy to automatically update the UI.
function reactive(obj) { return new Proxy(obj, { set(target, prop, value) { console.log(`Property ${prop} updated to ${value}`); return Reflect.set(target, prop, value); } }); } const data = reactive({ count: 0 }); data.count = 1; // Output: "Property count updated to 1" -
Function Call Interception:
- Use the
applyinterceptor to listen for function calls.
function sum(a, b) { return a + b; } const proxySum = new Proxy(sum, { apply(target, thisArg, args) { console.log(`Function called with arguments ${args}`); return Reflect.apply(target, thisArg, args); } }); proxySum(2, 3); // Output: "Function called with arguments 2,3", returns 5 - Use the
Notes
- Performance Impact: Proxy interception operations are slower than direct object manipulation; avoid overuse in performance-sensitive scenarios.
- Target Object Immutability: A Proxy intercepts operations on the target object, but the target object itself can still be modified directly (unless frozen).
Summary
Proxy and Reflect provide powerful metaprogramming capabilities. By intercepting object operations, they enable advanced functionalities such as validation, logging, and reactive systems. Combining them with Reflect to invoke default behavior ensures code simplicity and reliability.