Introduction
In JavaScript, the proxy object allows you to create custom behavior for basic actions without having direct access to them. The Proxy object lets developers specify custom behavior by intercepting or acting as a proxy in between core activities.
On the other hand, Reflect is a built-in object that provides interceptable methods for JavaScript actions.
Let’s learn how proxy and reflect work in Javascript.
What are Proxy and Reflect?
Proxy and Reflect are two reflection objects that are meant to work together. At runtime, a reflection object can be used to alter variables, attributes, and methods of objects.
Proxy
A Proxy object encapsulates another object and intercepts activities such as reading/writing properties and others, either handling them independently or letting the object handle them transparently.
Syntax:
let proxy = new Proxy(target, handler)
target - This is an object intended to wrap. It can be anything, including functions.
handler - This is an object that modifies the fundamental behavior of the proxied object or function. It is an object with traps and methods that intercept operations. For example, get trap for reading a property of target, set trap for writing a property into the target, and so on.
If a corresponding trap in the handler exists for operations on the proxy, it will execute, allowing the proxy to handle it; otherwise, the operation will be done on target.
Let’s create a proxy without a trap.
let target = {};
let proxy = new Proxy(target, {}); // Empty Handler
proxy.test = 15; // Writing To Proxy
console.log(target.test); // The Property Appeared In Target
console.log(proxy.test); // It can also be read from Proxy
for(let k in proxy) console.log(k); // Iteration Over Proxy
Output:
Because there are no traps, all proxy activities are forwarded to the target.
1.) The target value is set by the writing operation proxy.test=.
2.) The value from the target is returned by the reading operation proxy.test.
3.) Values from the target are returned by iterating over proxy.
Proxy is a unique type of "exotic object". It doesn't have any properties of its own. It transparently forwards operations to target with an empty handler.
To add more functionality to the Proxy, let’s add traps in the proxy.
An "internal method" defines how it works at the lowest level in the JavaScript specification. For example, [Get], which is an internal method for reading a property, [Set], which is an internal method for writing a property, and so on. We can't call these methods by name because they're only utilized in the specification.
Using GET Trap
To intercept reading, the handler should have a method get(target, property, receiver).
When a property is read, it triggers the following arguments:
target: The target object is the one supplied as the first argument to the new Proxy.
property: Property name.
receiver: If the target property is a getter, a receiver is an object that will be used like this in the call. It's usually the proxy object itself (or an object that inherits from it, if we inherit from proxy).
To implement default values for an object, let's use get.
We'll create a numeric array that returns -1 for values that don't exist.
Normally, if you try to access an array item that doesn't exist, you'll get undefined, but we'll wrap a regular array within the proxy that traps reading and returns -1 if the property doesn't exist:
let arr = [10, 20, 30, 40, 50];
arr = new Proxy(arr, {
get(target, prop) {
if (prop in target) {
return target[prop];
} else {
return -1; // default value
}
}
});
console.log( arr[0] );
console.log( arr[4] );
console.log( arr[25] ); // Accessing the out of index item.
Output:
As shown in the above output, we get the array's 0th and 4th index values. But when we are trying to access the 25th index, we get value -1 (default value we set using GET).
Using SET Trap
Let's assume we want a number-only array. There should be an error if a value of a different type is inserted.
When a property is written, the set trap triggers.
Syntax:
set(target, property, value, receiver)
target: It is the target object, which is supplied as the first argument to create a Proxy.
property: Property name.
value: Property value.
receiver: same to get trap, but only affects setter properties. If the setting is successful, the set trap should return true and false otherwise (it triggers TypeError).
Let's see how we may utilize it to check new values:
let arr = [];
arr = new Proxy(arr, {
set(target, prop, val) { // to intercept property writing
if (typeof val == 'number') {
target[prop] = val;
return true;
}
else {
return false;
}
}
});
arr.push(10); // Added Successfully
arr.push(20); // Added Successfully
arr.push(30); // Added Successfully
console.log("Length of array is: " + arr.length);
arr.push("test"); // Inserting number other than type number.
console.log("This line is never reached (error in the line above)");
Output:
We can with the help of Proxy, it can easily throw an error if another data type value is inserted in the array.
Now that we have covered proxy, it's time to learn Reflect.
Reflect
Reflect is a built-in object that provides interceptable methods for JavaScript operations. It isn't a function object, and it doesn't have an internal construct method. This implies you can't utilize Reflect objects as functions or even use the new operator with them. Proxy handlers are made easier with the help of Reflect objects.
Internal methods such as [Get], [Set], and others, as previously stated, are specification-only and cannot be called directly. This is made possible in some ways via the Reflect object. Its methods are bare-bones wrappers for internal methods.
There is a Reflect method with the same name and arguments as the Proxy trap for every internal method that Proxy can trap. As a result, we may use Reflect to send an operation to the original object.
In this example, both traps get and set forward reading/writing operations to the object transparently (as if they didn't exist), producing the message:
let user = {
name: "Abhishek",
city: "Kolkata"
};
user = new Proxy(user, {
get(target, prop, receiver) {
console.log(`GET ${prop}`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, val, receiver) {
console.log(`SET ${prop}=${val}`);
return Reflect.set(target, prop, val, receiver);
}
});
let name = user.name; // Shows "GET name"
user.name = "Rajat"; // Shows "SET name=Rajat"
let city = user.city; //Shows "GET city"
user.city = "Delhi" // Shows "SET city=Delhi"
Here,
Reflect.get reads an object property.
Reflect.set writes an object property and returns true if successful, false otherwise.
Output: