DEV Community šŸ‘©ā€šŸ’»šŸ‘Øā€šŸ’»

DEV Community šŸ‘©ā€šŸ’»šŸ‘Øā€šŸ’» is a community of 966,904 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Kade Esterline
Kade Esterline

Posted on

Proxy Objects in JavaScript

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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){}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
joelbonetr profile image
JoelBonetR

Nice explanation, thank you for sharing! šŸ˜

In defense of the modern web

I expect I'll annoy everyone with this post: the anti-JavaScript crusaders, justly aghast at how much of the stuff we slather onto modern websites; the people arguing the web is a broken platform for interactive applications anyway and we should start over;

React users; the old guard with their artisanal JS and hand authored HTML; and Tom MacWright, someone I've admired from afar since I first became aware of his work on Mapbox many years ago. But I guess that's the price of having opinions.