The first time I discovered JavaScript Proxy powers
By: Lior Agami
Tags: Creativity, JavaScript, Proxy, Problem Solving, Complexity

Photo by Nikita Kachanovsky on Unsplash.

This journey began when I was writing about Javascript’s Object and Map data types. My main goal was to demonstrate some best practices and handy tricks that we could use, but more importantly, I wanted to show how we choose between data types and why.

During my writing, I talked about getting the Map’s size with its built-in .size() method, and for an Object, there are several ways to do that. One of them is by using - Object.values(obj).length . This method allows us to get an Object’s length by converting it to an Array and use its .length property.

The problem

In my opinion, one of the downsides of the Object data type is that a Javascript Object doesn’t have a .length property. The .length property exists for arrays but objects do not contain it.

Ex:

const arrLength = [1,2,3].length; // 3

const objLength = {a:1,b:2,c:3}.length; // undefined

As I’ve mentioned earlier, Object.values() method returns an Array out of the parameters that you passed in. In our case — an Object.

But what is happening under the hood when we use this method?

Let’s talk about it.

A deep dive into the ocean

Take a look at Ecmascript’s 2021 language specifications (here).

When we call Object.values() three main operations occur :

1. The abstract operation ToObject takes our argument and converts it to a value of type Object.

2. The abstract operation EnumerableOwnPropertyNames takes the Object, iterates over it, doing a process (which you can find in the specs), and returns properties.

3. The abstract operation CreateArrayFromList takes those properties (list), creating an array from this list, and returns it. It is used to create an Array object whose elements are provided by elements.

Let’s talk a bit about the complexity

We can see that whenever we are using Object.values() , no matter the Object’s length, it’s getting iterated over more than once but with non-nested loops. We Describe this type of operation by using the Big O notation as O(n) — read about it here.

A quicky about Big O notation -

Big O notation is a mathematical notation that describes the limiting behavior of a function when the argument tends towards a particular value or infinity. — Wikipedia.

Simply put, the number of operations that will be executed on this object is unknown. As the object grows, the operation’s growth will be linear.

Remember that we want to achieve that O(1) — whatever the Object’s size will be, we want to execute only one operation. Make sense?

So how does the Proxy object fit in?

First, let's see what happens if we don’t use the Proxy object:

So, we want to use an object and still have access to the .length property in a way that would not cost too many operations under the hood.

If using an object it will look like the following:

This code sample isn’t the best approach because we always need to take care of the .length property by calling the manageLength function. In certain cases (Ex: if we remove key-value pairs from this object), it won’t go as expected. We will need to manually call manageLength function since we can’t handle an object property deletion with a setter/getter kind of function. Probably we will find ourselves with an endless circle of bugs and unnecessary efforts to manage this type of operation. I hope I made myself clear.

Let’s get a bit familiar with the Proxy object.

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

A Proxy is created with two parameters:

Target: the original object which you want to proxy.

Handler: An object that defines which operations will be intercepted and how to redefine intercepted operations.

It may sound frightening, but I promise that you will understand Javascript Proxy’s usage at the end of this article, and you’ll not be afraid anymore 😄.

Let's take a look at a basic implementation of a Proxy:

We declared a users object that holds two key-value pairs. We also declared a handler object with a getter function that returns a string and invokes when accessing the Proxy.

Now let’s see how we solve our issue with the object’s length we want to maintain and manage, and how we work-around that traditional approach of Object.values(obj).length with the Proxy object.

The solution

Let’s see how we nail our goal (keep having the .length property within our object), reduce time complexity, and avoid unnecessary operations.

Amazing. What happened was that we declared a setter method that took care of the object’s property assignments and another method that took care of the object’s property deletion (note that objects don’t have this option).

With Proxy’s built-in methods, we can have much more control of our target Object and maintain the .length property using readable and efficient code that will not cost us too many operations and reduces time complexity.

In summary

The features Proxy provides genuinely remarkable, and you may find it very useful in different situations. Now that you know the basics and you have in mind what a Proxy is capable of, you can think of implementing it when needed.

But most important:

The main point was to encourage you — my dear readers, to use your tools wisely, get familiar with them, and don’t be afraid to use one for the first time.

Usually, there are several ways to solve a problem. No matter if you solved a particular problem already — question it, examine other ways, try to think outside the box for a minute, and I guarantee that you will become a better version of yourself.

Thanks for reading! ❤🤘