Proxy Objects in JavaScript

javascript, programming

3 minute read

08/18/2022

Proxy objects were unknown territory for me just a little while ago. I had never even heard of them until I was asked to explain how to use them over on TikTok. I've spent some time now reading up on how to create proxy objects and how they can be used. I'm going to try my best to keep this brief so this won't be a comprehensive guide on how to use proxy objects. This will however show you some of the basic ways you can use them, as well as provide a few examples.

Creating a Proxy Object

To create a proxy object you'll need two things first, a target object and a handler object. The target is the object you're wanting to make the proxy for and the handler is where you define what behavior to change and how it should be changed. After defining both of those objects, you can create the proxy by creating a variable set equal to new Proxy(target, handler) replacing target and handler with whatever you've named them.

const target = {
  greetingOne: "hello",
  greetingTwo: "world",
};

const handler = {};

const proxy = new Proxy(target, handler);

I've left the handler empty for now, meaning that there won't be any changes in behavior. This isn't very useful, so next we'll start looking at how we can change some of the behavior of the proxy.

Changing The Get Method

Let's say that we want our proxy to change greetingTwo to 'dev.to' if target.greetingTwo is equal to 'world'. To do this, we'll redefine the get method in our handler object to overwrite the normal behavior. We'll need to pass target, prop and receiver into a get() function and then use those parameters to change the behavior.

const target = {
  greetingOne: "hello",
  greetingTwo: "world",
};

const handler = {
  get(target, prop, receiver) {
    if (prop === "greetingTwo") {
      return "dev.to";
    }
    return Reflect.get(...arguments);
  },
};

const proxy = new Proxy(target, handler);

console.log(proxy.greetingTwo); // dev.to

Using Reflect we can keep the original behavior for some of the accessors and only change the values we're wanting to change.

Changing The Set Method

What if we only want certain values to be valid on an object? In order to accomplish this, we can use set() in our handler. This time we'll pass in an object, prop and value and use the parameters to make sure our data is valid.

const target = {
  value: 1,
};

const handler = {
  set(obj, prop, value) {
    if (prop === "value" && value < 1) {
      console.log("value cannot be lower than 1");
      // handle error
    } else {
      return Reflect.set(...arguments);
    }
  },
};

const proxy = new Proxy(target, handler);

proxy.value = 0; // 'value cannot be lower than 1'
console.log(proxy.value); // 1

proxy.value = 3;
console.log(proxy.value); // 3

Again, using Reflect let's us keep the normal behavior if we want it. Attempting to set proxy.value to a number less than 1 will result in a warning being console logged and the value not being set. Values 1 and higher are still valid.

Intercepting other methods

In hopes of keeping this post short and concise I'm not going to cover each individual handler trap. There are eleven more, but I'll only be showing a few. MDN has examples for all of them, and I would reccomend giving the docs a quick read if you're looking for more examples and a more thorough explanation.

  • apply()

    • Using apply() traps function calls.
    • Using a function as your target object, and calling apply() like this:
    apply: function(target, thisArg, argList){}
    

    you can add onto existing functions, changing how a function behaves.

  • construct()

    • Using construct(target, args) traps the 'new' keyword.
    • You're then able the change the behavior of constructing new objects
  • has()

    • Using has(target, key) is a trap for the in operator
    • You're then able to change the behavior of how keys are accessed through the in operator like this
    if (key === "keyOne") {
      return false;
    }
    return key in target;
    
  • deleteProperty()

    • Using deleteProperty(target, property) traps the delete operator
    • This will allow you to change how properties get deleted, allowing for conditions to be set
    delete proxy.keyOne; // gets trapped in the handler object
    

That's going to do it for this post, If you have any questions I'll do my best to answer them in the comments.

If you have any questions please leave a comment over on dev.to

Built with Next.js, Tailwind and Vercel