- Published on
Proxy in JavaScript
- Authors
- Name
- Ibrahim Motani
But what does Proxy even mean?
Before getting into any technical details, let us understand what does the word Proxy mean without any context? The word proxy means a person or a thing that represents someone else. The proxy also has the authority to act on behalf of another person or entity.
Now let us think of Proxy in terms of JavaScript. A Proxy is an object that can represent and act on behalf of it’s original object. The proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations on that object. These proxies can redefine fundamental operations like getting, setting and defining properties. Let us take a look at an example.
const target = {
message1: "hello",
message2: "world",
};
const handler = {
Reflect.get(target, prop, receiver) {
return "everyone";
},
};
const proxy = new Proxy(target, handler2);
Let us understand the above code example and terminologies used when talking about Proxy:
- Target - The
originalobject that you want to target or you want to create a proxy for. Thetarget objectin the above code example is the target here. - Handler - Handler is an object which has
methodsthat willintercept and modify the operationson thetargetobject. These methods are also calledtraps. These methods redefine theintercepted operations. - Reflect - The
Reflectobject was introduced in ES6 to provide an easier manner tobuild handler methods. Reflect object is awrapperoverinternalJavaScript methods. We will understand more about these internal methods and the Reflect object as we move further in this blog.
Why do we need Proxy? What problem does it solve?
In my opinion, Proxy solves two major problems:
Customisation
Proxy enables
customisationof the behaviour of objects. It addresses the problem of customisation by allowing us tomodify fundamental operationson an object. This provides us a flexible mechanism to alter the behaviour of objects as per our needs. Let’s consider a scenario where you want to store only strings in an object and these strings should be stored inuppercase. This can be achieved by Proxy like the code below// the object for which the proxy will be created const target = {}; // handler const handler = { set(target, prop, value) { // intercept write operation and converts string to uppercase const uppercaseValue = typeof value === 'string' ? value.toUpperCase() : value; // returns a boolean to indicate if the write was successful return Reflect.set(target, prop, uppercaseValue); } }; // create a proxy object with modified write operation const proxy = new Proxy(target, handler); // write proxy.name = "Ibrahim"; proxy.city = "Jamnagar"; console.log(proxy.name); // IBRAHIM console.log(proxy.city); // JAMNAGARLet us consider another scenario where we want to
reada property from the object but it doesn’t exist. We don’t want it to returnundefined. To achieve this, we can make use of Proxy and modify the[[Set]]method to achieve the same. Similar mechanisms can be used for data validation and input sanitization as well.// target const target = {}; // customize write property in the handler const handler = { get(target, prop, receiver) { // if the property doesn't exist create it with a default value if (!(prop in target)) { target[prop] = "Naruto"; } // continue with the default behavior for reading property return Reflect.get(target, prop, receiver); } }; // create a proxy const defaultObject = new Proxy(target, handler); // read property console.log(defaultObject.name); // 'Naruto'Security and Encapsulation
Direct access to object properties can lead to unintentional modifications. Proxy allows us to implement access control by setting policies. Let us take a look at an example where we don’t want the private properties of the object to be accessed by every part of the code. Let us see how to achieve it with Proxy
const target = { _secretKey: 123, normalKey: 456 }; const handler = { get(target, prop, receiver) { if (prop.startsWith("_")) { throw new Error('Access to private property denied'); } return Reflect.get(target, prop, receiver); }, set(target, prop, value, receiver) { if (prop.startsWith("_")) { throw new Error('Modification of private property denied'); } return Reflect.set(target, prop, value, receiver); } }; const proxy = new Proxy(target, handler); console.log(proxy._secretKey); // throws an error console.log(proxy.normalKey); // 456
Reflect Object and Internal methods
So now we know that Proxy is an an object with trap functions that will intercept and modify the interactions with the target object. But what all calls can be trapped in a proxy is? For all operations/interactions with the object, there’s a corresponding internal method in JavaScript that describes how it works at the lowest level. For example, when we access the message1 propety from the target object like this target.message1, the internal [[Get]] method is called. Smilarly, when we want to assign a new value to the message1 property like target.message1 = ‘How are you?’ , the internal method [[Set]] is called.
Following is a list of internal methods that can be trapped in the handler parameter. The fist column is the name for internal method. The second column is the name of the method that we can use in the handler function to modify the internal methods. The third column will tell us at which interaction with the object the corresponding internal method is triggered.
| Internal Method | Handler | Triggers when |
|---|---|---|
| [[Get]] | get() | Reading a property |
| [[Set]] | set() | Writing a property |
| [[HasProperty]] | has() | in operator |
| [[Delete]] | deleteProperty() | delete operator |
| [[Call]] | apply() | Function call |
| [[Construct]] | construct() | new operator |
| [[GetPrototypeOf]] | getPrototypeOf() | Object.getPrototypeOf |
| [[SetPrototypeOf]] | setPrototypeOf() | Object.setPrototypeOf |
| [[IsExtensible]] | isExtensible() | Object.isExtensible |
| [[DefineOwnProperty]] | defineProperty() | Object.defineProperty, Object.defineProperties |
| [[GetOwnProperty]] | ownKeys() | Object.getOwnPropertyNames, for..in, Object.keys/values/entries |
All of the above mentioned properties can be intercepted and modified according to our needs with the Reflect Object.
That’s all for today folks. Thank you for reading. See you in the next one 👋