Everything You Should Know About JavaScript Proxy

Get started using Proxy in your JavaScript code today

Gourav Kajal
Oct 29 · 10 min read
Image for post
Image for post
Uploaded by Oskar Yildiz on Unsplash

First things first: The official definition of Proxy from MDN is:

“The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object.”

Now, before going in to dig deeper, let’s first discuss some real-life examples so that at the end, we can have better clarity about Proxy. This is how bitfish explained it in his article.

As human beings, we have a lot of things to do in our day-to-day life, such as reading emails, receiving a delivery, etc. Sometimes, we may feel a little anxious because of a lot of extra and unnecessary tasks — like there are a lot of spam emails and it takes some effort and time to get rid of them, or the delivery received may contain bombs planted by terrorists, threatening our security (just a possibility).

That’s where you want someone to protect you from these kinds of threats: a loyal housekeeper. We need someone who does something extra for us to protect what we’re doing.

Now, let’s back to basics — JavaScript. We know that we can extend JavaScript to use features provided by the object-oriented programming paradigm such as encapsulation, abstraction, classes, objects, and so on. We can say that every JavaScript developer uses objects and it’s pretty common to save some info in objects.

But when we do so (use objects), our code becomes less secure. Because JavaScript objects always run naked and you can do anything with them.

So, to tackle this problem, a new feature called Proxy was introduced in ECMAScript 2015, commonly known as ES6. With Proxy, we can find a loyal housekeeper for the object and help us enhance the original functions of the object.

What Is a JavaScript Proxy Object?

“A JavaScript Proxy is an object that wraps another object (target) and intercepts the fundamental operations of the target object.” — Javascript Tutorial

For a person, we might have operations like reading mail, picking up delivery, etc., and the housekeeper can do that for us. For an object, the fundamental operations can be the property lookup, assignment, enumeration, and function invocations, etc., which can also be enhanced by the proxy object.

Image for post
Image for post
A simple illustration of Proxy Object

Creating a proxy object

At most basic, you can use the following syntax to create a Proxy.

let proxy = new Proxy(target, handler);

where

  • target is an object to wrap
  • handler is an object that contains methods to control the behaviors of the target. The methods inside the handler object are called traps.

A Proxy creates an undetectable barrier around the target object that redirects all operations to the handler object. If we send in an empty handler, the proxy is just an empty wrapper around the original object.

A simple proxy example

First of all, let's define a new object called user.

const user = { 
firstName: ‘John’,
lastName: ‘Doe’,
email: ‘john.doe@example.com’,
}

Now, define a handler object:

In the handler, we can list the actions we want to proxy. For example, if we want to print out a statement in the console while getting an object property, we can write like this:

const handler = {     
get(item, property, itemProxy) {
console.log(`Property ${property} has been read.`);
return target[property];
}
}

The get function can take three arguments:

  • item : it is the object itself.
  • proerty : the name of the property you are trying to read.
  • itemProxy : it is the housekeeper object that we just created.

Now, create a proxy object, which is very simple to do, like this:

const proxyUser = new Proxy(user, handler);

The proxyUser object uses the user object to store data. The proxyUser can access all properties of the user object

Image for post
Image for post
Illustration of our example code above

Now, let’s just access the firstName and lastName properties of the user object via the proxyUser object:

console.log(proxyUser.firstName);
console.log(proxyUser.lastName);

The output will look something like this:

Property firstName has been read. 
John
Property lastName has been read.
Doe

In the above example, the return value of the get function is the result of reading this property. Because we don’t want to change anything yet, we just return the property’s value of the original object.

We can also change the result if we have to. For example, we can do this:

let obj = {a: 1, b:2}let handler = {
get: function(item, property, itemProxy){
console.log(`You are getting the value of '${property}' property`)
return item[property] * 2
}
}
let objProxy = new Proxy(obj, handler)console.log(objProxy.a)
console.log(objProxy.b)

It will output this:

You are getting the value of 'a' property 
2
You are getting the value of 'b' property
4

Proxy Traps

The get() trap

The get() trap is fired when a property of the target object is accessed via the proxy object.

In the previous example, a message is printed out when a property of the user object is accessed by the proxyUser object.

The set() trap

In addition to intercepting reads to properties, we can also intercept modifications to properties. Like this:

let obj = {a: 1, b:2}let handler = {
set: function(item, property, value, itemProxy){
console.log(`You are setting '${value}' to '${property}' property`)
item[property] = value
}
}
let objProxy = new Proxy(obj, handler)

Now, if we try to update the value of the property, we’ll see the output like this:

Image for post
Image for post

Because we need to pass an extra value when setting the value of the property, the set function above takes one more argument than the get function.

In addition to intercepting reads and modifications to properties, Proxy can intercept a total of 13 operations/traps on objects.

They are:

  • get(item, propKey, itemProxy): Intercepts the reading operation of object properties, such as obj.a and ojb['b']
  • set(item, propKey, value, itemProxy): Intercept the setting operation of object properties, such as obj.a = 1 .
  • has(item, propKey): Intercept the operation of propKey in objProxy and return a boolean value.
  • deleteProperty(item, propKey): Intercept the operation of delete proxy[propKey] and return a boolean value.
  • ownKeys(item): Intercept the operations such as Object.getOwnPropertyNames(proxy),Object.getOwnPropertySymbols(proxy),Object.keys(proxy),for...in, return an array. The method returns the property names of all the target object’s own properties, while the return result of Object.keys() includes only the target object’s own enumerable properties.
  • getOwnPropertyDescriptor(item, propKey): Intercept the operation of Object.getOwnPropertyDescriptor(proxy, propKey), return the property’s descriptor.
  • defineProperty(item, propKey, propDesc): Intercepter these operations :Object.defineProperty(proxy, propKey, propDesc),Object.defineProperties(proxy, propDescs) , return a boolean value.
  • preventExtensions(item): Intercepter the operation of Object.preventExtensions(proxy), return a boolean value.
  • getPrototypeOf(item): Intercepter the operation of Object.getPrototypeOf(proxy),return an object.
  • isExtensible(item): Intercepter the operation of Object.isExtensible(proxy),return a boolean value。
  • setPrototypeOf(item, proto): Intercepter the operation of Object.setPrototypeOf(proxy, proto),return a boolean value。

If the target object is a function, there are two additional operations to intercept.

  • apply(item, object, args): Intercept function call operations, such asproxy(...args),proxy.call(object, ...args),proxy.apply(...) .
  • construct(item, args): Intercept the operation invoked by a Proxy instance as a constructor, such as new proxy(...args).

Now let’s get into some of the use cases and see what Proxycan actually do for us. Following are the use-cases shared by bitfish in his article, where Proxy can be a savior.

Implements a Negative Index of an Array

Some of the programming languages, such as Python, support the negative indexes on arrays.

A negative index takes the last position of the array as the starting point and counts forwards. Such as:

  • arr[-1] is the last element of the array.
  • arr[-4] is the fourth element in the array from the end.

This is no dought a powerful and useful feature. But unfortunately, array negative indexes are not supported by JavaScript right now.

If you try to do so, you’ll get undefined, like this:

Image for post
Image for post

Here, Proxy can be very useful if we really have to use negative indexes in our code.

We can wrap an array as a Proxy object. When a user tries to access a negative index, we can intercept this operation through the Proxy’s get method. The negative index is then converted to a positive index according to the previously defined rules, and the access is completed.

Let’s see how exactly we can achieve this using Proxy.

So our requirement is: when the user tries to access a property that is the index of an array, and it is found to be a negative index, then intercept and deal with it accordingly. If the property is not an index, or if the index is positive, we don’t do anything.

First of all, Proxy’s get method will intercept access to all properties of the array, including access to an index of the array and access to other properties of the array. An operation that accesses an element in an array is performed only if the property name can be converted to an integer. We actually need to intercept this operation to access the elements in the array.

We can determine if a property of an array is an index by check if it can be converted into an integer.

Number(propKey) != NaN && Number.isInteger(Number(propKey))

Here’s the complete code:

function negativeArray(array) {
return new Proxy(array, {
get: function(target, propKey){
if (Number(propKey) != NaN && Number.isInteger(Number(propKey)) && Number(propKey) < 0) {
propKey = String(target.length + Number(propKey));
}
return target[propKey]
}
})
}

Let’s see the example in Chrome’s Developer Tool.

Image for post
Image for post

Data Validation

As we know, javascript is a weakly typed language. Normally, when an object is created, it runs naked. Anyone can modify it.

But most of the time an object’s property value is required to meet certain conditions. For example, an object that records user information should have an integer greater than 0 in its age field, normally less than 150.

let person1 = {
name: 'Jon',
age: 23
}

By default, however, JavaScript does not provide a security mechanism, and you can change this value at will.

person1.age = 9999
person1.age = 'hello world'

In order to make our code more secure, we can wrap our object with Proxy. We can intercept the object’s set operation and verify whether the new value of the age field conforms to the rules.

Here’s how exactly we can do it with code:

let ageValidate = {
set (item, property, value) {
if (property === 'age') {
if (!Number.isInteger(value) || value < 0 || value > 150) {
throw new TypeError('age should be an integer between 0 and 150');
}
}
item[property] = value
}
}

Now we try to modify the value of this property, and we can see that the protection mechanism we set is working.

Image for post
Image for post

Associate Property

Many times, the properties of an object are related to each other. For example, for an object that stores user information, its postcode and location are two highly correlated properties. When a user’s postcode is determined, his location is also determined.

To accommodate readers from different countries, I use a virtual example here. Assume that the location and postcode have the following relationship:

JavaScript Street  --  232200Python Street -- 234422Golang Street -- 231142

This is the result of expressing their relationship in code.

const location2postcode = {
'JavaScript Street': 232200,
'Python Street': 234422,
'Golang Street': 231142
}
const postcode2location = {
'232200': 'JavaScript Street',
'234422': 'Python Street',
'231142': 'Golang Street'
}

Then look at an example:

let person = {
name: 'Jon'
}
person.postcode = 232200

We want to be able to trigger person.location='JavaScript Street' automatically when we set person.postcode=232200 .

Here’s the solution:

let postcodeValidate = {
set(item, property, value) {
if(property === 'location') {
item.postcode = location2postcode[value]

}
if(property === 'postcode'){
item.location = postcode2location[value]
}
}
}
Image for post
Image for post

So we’ve bound the postcode and location together.

Private Property

We know that private properties have never been supported in JavaScript. This makes it impossible for us to manage access rights reasonably when we are writing code.

To solve this problem, the JavaScript community’s convention is that fields that begin with the character _ are considered as private properties.

var obj = {
a: 1,
_value: 22
}

The _value property above is considered private. It is important to note, however, this is just a convention, and there is no such rule at the language level.

Now that we have the Proxy, we can simulate the private property feature.

Compared to ordinary properties, private properties have the following features:

  • The value of this property cannot be read
  • When the user tries to access the object’s key, the property is not noticeable

We can then examine the 13 intercept operations of Proxy that we mentioned earlier and see that there are 3 operations that need to be intercepted.

function setPrivateField(obj, prefix = "_"){
return new Proxy(obj, {
// Intercept the operation of `propKey in objProxy`
has: (obj, prop) => {},
// Intercept the operations such as `Object.keys(proxy)`
ownKeys: obj => {},
//Intercepts the reading operation of object properties
get: (obj, prop, rec) => {})
});
}

We then add the appropriate judgment statement to the template: if the user is found trying to access a field that starts with _, access is denied.

function setPrivateField(obj, prefix = "_"){
return new Proxy(obj, {
has: (obj, prop) => {
if(typeof prop === "string" && prop.startsWith(prefix)){
return false
}
return prop in obj
},
ownKeys: obj => {
return Reflect.ownKeys(obj).filter(
prop => typeof prop !== "string" || !prop.startsWith(prefix)
)
},
get: (obj, prop) => {
if(typeof prop === "string" && prop.startsWith(prefix)){
return undefined
}
return obj[prop]
}
});
}

Here’s the final code for the example:

Image for post
Image for post

Conclusion

We have learned what exactly Proxy is in JavaScript, what are some of its use cases. We now know how we can use a proxy to spy on objects. You should now be able to add behaviors to them by using trap methods in the handler object. I hope you’re now inspired enough to explore the possibilities with Proxy in JavaScript.

Thanks for reading. I hope this article was helpful to you. Please feel free to put some questions or suggestions in response.

Better Programming

Advice for programmers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store