loading...
Cover image for Learn the JavaScript Proxy Object: Creating Immutable Objects

Learn the JavaScript Proxy Object: Creating Immutable Objects

nas5w profile image Nick Scialli (he/him) Originally published at typeofnan.dev ・3 min read

While JavaScript allows us to mutate objects, we might choose to not allow ourselves (and fellow programmers) to do so. One of the best examples of this in the JavaScript world today is when we're setting state in a React application. If we mutate our current state rather than a new copy of our current state, we can encounter hard-to-diagnose issues.

In this post, we roll our own immutable proxy function to prevent object mutation!


If you enjoy this post, please give it a πŸ’“, πŸ¦„, or πŸ”– and consider:


What is Object Mutation?

As a quick refresher, object mutation is when we change a property on an object or array. This is very different from reassignment, in which we point a different object reference altogether. Here are a couple examples of mutation vs. reassignment:

// Mutation
const person = { name: "Bo" };
person.name = "Jack";

// Reassignment
let pet = { name: "Daffodil", type: "dog" };
pet = { name: "Whiskers", type: "cat" };

And we have to keep in mind this applies to arrays as well:

// Mutation
const people = ["Jack", "Jill", "Bob", "Jane"];
people[1] = "Beverly";

// Reassignment
let pets = ["Daffodil", "Whiskers", "Ladybird"];
pets = ["Mousse", "Biscuit"];

An Example of Unintended Consequences of Object Mutation

Now that we have an idea of what mutation is, how can mutation have unintended consequences? Let's look at the following example.

const person = { name: "Bo" };
const otherPerson = person;
otherPerson.name = "Finn";

console.log(person);
// { name: "Finn" }

Yikes, that's right! Both person and otherPerson are referencing the same object, so if we mutate name on otherPerson, that change will be reflected when we access person.

Instead of letting ourselves (and our fellow developers on our project) mutate an object like this, what if we threw an error? That's where our immutable proxy solution comes in.

Our Immutable Proxy Solution

The JavaScript Proxy object is a handy bit of meta programming we can use. It allows us to wrap an object with custom functionality for things like getters and setters on that object.

For our immutable proxy, let's create a function that takes an object and returns a new proxy for that object. when we try to get a property on that object, we check if that property is an object itself. If so, then, in recursive fashion, we return that property wrapped in an immutable proxy. Otherwise, we just return the property.

When we try to set the proxied object's value, simple throw an error letting the user know they can't set a property on this object.

Here's our immutable proxy function in action:

const person = {
  name: "Bo",
  animals: [{ type: "dog", name: "Daffodil" }],
};

const immutable = (obj) =>
  new Proxy(obj, {
    get(target, prop) {
      return typeof target[prop] === "object"
        ? immutable(target[prop])
        : target[prop];
    },
    set() {
      throw new Error("Immutable!");
    },
  });

const immutablePerson = immutable(person);

const immutableDog = immutablePerson.animals[0];

immutableDog.type = "cat";
// Error: Immutable!

And there we have it: we're unable to mutate a property on an immutable object!

Should I Use This In Production

No, probably not. This kind of exercise is awesome academically, but there are all sorts of awesome, robust, and well-tested solutions out there that do the same thing (e.g., ImmutableJS and ImmerJS). I recommend checking out these awesome libraries if you're looking to include immutable data structures in your app!


If you enjoy this post, please give it a πŸ’“, πŸ¦„, or πŸ”– and consider:


Posted on Jun 2 by:

nas5w profile

Nick Scialli (he/him)

@nas5w

Husband, dog dad, software engineer, coffee monster. Working in civic tech!

Discussion

markdown guide
 

Nice. Did not know Proxy object. Is this approach equal to Object.freeze()?

 

Object.freeze is shallow, so it’ll only make first level props immutable. Anything deeper would need to be recursively frozen.