Understanding JavaScript Proxy. Preserving backward compatibility in a library

JavaScript

While learning about various JavaScript features, it is usually not crucial to know them by heart. Sometimes it is very beneficial just to know what’s out there and what can we use it for. One of those features is a JavaScript Proxy. In this article, we go through its functionalities and figure out some of its use-cases.

The purpose of JavaScript Proxy

The core concept of a Proxy is that it aims to define custom behavior for basic operations. It is not only one of the ES6 features that we can use to change some of the ways the JavaScript language operates. In one of the previous articles, we also talk about how we change some of the language behavior with symbols.

The code above contains a few concepts that we need to get the hang of to work with Proxies.

The first of them is a target. It is an already existing object – we want its behavior to change in some way. It can be any object you want – an array, a function, or another JavaScript proxy.

The other is a handler. It contains methods that alter the behavior of the target. We refer to the above functions as traps.

The name trap comes from the concept within the operating system.

get

One of the most basic examples is a get trap. It intercepts the operation of getting a property value.

In the above code, we create a proxy that extends the behavior of the translations object. Now, if the desired string is not found, we get a default translation.

set and defineProperty

Other traps that might come in handy are set and deleteProperty. With them, we can change the behavior of delete operator and how setting the properties works.

The set method should return a boolean value. Return true to indicate that assignment succeeded.

The deleteProperty method must return a Boolean indicating whether or not the property has been successfully deleted.

In the simple example above, we have an object that stores a resize listener. It might make sense to automatically attach the event listener to the window object when we set the   property and clean up when we delete it.

has

In the examples above, we use the in operator. We can change its behavior with a proxy!

A common approach to private fields in JavaScript is to add an underscore in the property name. We might want to hide them for the in operator.

The above way of defining private fields in JavaScript might soon change due to introducing native private fields

There are more ways in which you can use a proxy. For a complete list visit the MDN documentation.

The real-world use case

You might say that using a JavaScript proxy might make the code less readable. After all, somebody might not expect that there is some additional logic behind a simple get operation (even though we also have a concept of getters). I would agree in a lot of cases. Keeping the code simple is a lot more critical than using fancy language features.

Instead of creating a proxy to provide default translation, we could create a straightforward function. Some might argue that this is more readable than creating a proxy.

On the other hand, I recently found a bug in a library called lcid. Fixing it required changing the data structure that was the basis of the whole library. The main issue was that the data structure is also exported from the library. Since lcid is currently downloaded about 13 million times per week, I wanted to maintain some level of backward compatibility when creating a pull request.

Previously, the data structure looked like that:

I basically reverted it:

Since the whole data structure was exported so that anyone can use it as they see fit, two problems emerged:

  • the data structure is now inverted
  • the keys are now strings, not numbers

To fix the above, I inverted the data structure again programmatically and wrapped it in a proxy:

Our proxy performs a conversion from a string to the number when a get operation takes place. Thanks to that, even though we modify the underlying data structure, we keep some level of backward compatibility.

Summary

The most important thing to remember, in my opinion, is to try not to reinvent the wheel. If we can achieve something can with a more conventional approach, there is a good chance that the code will be more readable. If you keep that in mind, you still have a good chance of encountering a good use case for using a JavaScript proxy. My aim with that article was for you to be aware of what can you achieve with it and increase your JavaScript toolset.

avatar
  Subscribe  
Notify of