JavaScript Proxy

By  on  

I've always loved the flexibility of Objects and prototypes in JavaScript, but for a long time, I felt that a level of dynamism was lacking. JavaScript eventually added get and set methods for object properties, which was an awesome step, but there was still room for improvement.

The JavaScript Proxy API was an awesome improvement: a virtualizing interface to control the modification behavior of an object!

Proxy Format

Proxy accepts an object to proxy for and an object with handlers ("traps") for get, set, has and other common object methods:

const proxy = new Proxy({}, {
  get: (obj, prop) => { ... },
  set: (obj, prop, value) => { ... },
  // more props here
});

Any attempt to set or get a property is run through the trap, allowing you to run additional logic, especially if the property is unwanted, doesn't exist, or requires validation.

Basic Usage

Let's create a basic proxy that returns defaults for any given property:

const proxy = new Proxy({}, {
  get: (obj, prop) => {
    return prop in obj ? obj[prop] : null;
  }
});

// proxy.whatever => null

The example above illustrates that no matter the property the code attempts to set, your Proxy logic can capture and modify it as desired. Instead of undefined being returned for a property that doesn't exist, you can instead return null.

Validation

The most obvious and useful usage of Proxy is validation; since you monitor validate any property coming in, you can keep your data as pure as possible.

const proxy = new Proxy({}, { 
  set: (obj, prop, value) => {
    // Don't allow age > 100
    if (prop === "age" && value > 100) {
      // Set to max age
      value = 100;
    }
    obj[prop] = value;
  }
});

proxy.age = 120;
proxy.age; // 100

You can choose to modify incoming data like the example above, or you can throw an error:

const proxy = new Proxy({}, { 
  set: (obj, prop, value) => {
    // Ensure age is of type Number
    if (prop === "age" && isNaN(value)) {
      throw new Error("Invalid age value!");
      return;
    }
    obj[prop] = value;
  }
});

proxy.age = "yes";  // Uncaught error: Invalid age value!

Debugging

You can even use Proxy to provide yourself debugging points or events to see how and when values are being set and retrieved:

const proxy = new Proxy({}, { 
  set: (obj, prop, value) => {
    console.log(`Setting ${prop} from ${obj[prop]} to ${value}`);
    obj[prop] = value;
  }
});

proxy.prop = 1;
proxy.prop = 2;

// Setting prop from undefined to 1
// Setting prop from 1 to 2

Even if you don't modify any input or output, having a hook to value changes on an object is incredibly valuable.

Formatting

Another simple usage is formatting data that comes into the object:

const proxy = new Proxy({}, { 
  set: (obj, prop, value) => {
    if (prop === "age") {
      obj[prop] = Number(value);
    }
  }
});

proxy.prop = "1"; // 1

You can format from String to Number, Number to String, or simply set defaults.

Using Proxies with Existing Objects

In the examples provided above, we use an empty object ({}), but you can also use an existing object:

const myObj = { x: "x", y: "y" };

// Use existing object, simply set value as is given
const proxy = new Proxy(myObj, { 
  set: (obj, prop, value) => { 
    obj[prop] = value; 
  } 
});

// 
proxy.x = "XXX";
proxy.x; // "XXX"
myObj.x; // "XXX"

Note that the original object does change, as well as the proxy, so the proxy does not act as a "copy", so to speak.

People love to hate on PHP but one thing I loved about the language was "magic properties" that you could spy on and dynamically react to. The Proxy API feels like JavaScript's answer to that. The more you can control what's coming and going out, the better your application can become!

Recent Features

  • By
    Send Text Messages with PHP

    Kids these days, I tell ya.  All they care about is the technology.  The video games.  The bottled water.  Oh, and the texting, always the texting.  Back in my day, all we had was...OK, I had all of these things too.  But I still don't get...

  • By
    Create a CSS Flipping Animation

    CSS animations are a lot of fun; the beauty of them is that through many simple properties, you can create anything from an elegant fade in to a WTF-Pixar-would-be-proud effect. One CSS effect somewhere in between is the CSS flip effect, whereby there's...

Incredible Demos

  • By
    Create Twitter-Style Dropdowns Using jQuery

    Twitter does some great stuff with JavaScript. What I really appreciate about what they do is that there aren't any epic JS functionalities -- they're all simple touches. One of those simple touches is the "Login" dropdown on their homepage. I've taken...

  • By
    Create a Spinning, Zooming Effect with CSS3

    In case you weren't aware, CSS animations are awesome.  They're smooth, less taxing than JavaScript, and are the future of node animation within browsers.  Dojo's mobile solution, dojox.mobile, uses CSS animations instead of JavaScript to lighten the application's JavaScript footprint.  One of my favorite effects...

Discussion

  1. This looks really promising indeed. Do you have any idea if they work when setting it with { …object } ?

    • alix

      Nice question, i was wondering the same thing.I did a quick try in the console, and as expected, if you spread an object, it’s considered as a new object, so you don’t modify your basic object

      const test = {x: 'toto'};
      const proxy = new Proxy({...test}, { 
        set: (obj, prop, value) => { 
          obj[prop] = value; 
        } 
      });
      
      proxy2.x = 'titi';
      proxy2.x; //titi
      test.x;   //toto
      
  2. Todd

    Am I right in thinking that this could be used to observe changes to objects and trigger actions/events when they change? The developer would need to make sure they are updating the proxy, rather than the object it’s self, obviously. But this seems like a good solution. Are there any other solutions for watching changes to an object that would suit better than this?

  3. Ali

    The set trap should return a boolean value. If not, you will get an error (in strict mode)

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!