# An Introduction to JavaScript Proxies

A "proxy" intercepts messages between you and a particular system. You've possibly encountered the term "proxy server". It's a device between your web browser and a web server which can examine or change requests and responses. They often cache assets so downloads are faster.

JavaScript proxies arrived in ES2015. A proxy sits between an object and the code that uses it. You can use them for meta-programming operations such as intercepting property updates.

## Proxy quick start

Consider this simple JavaScript object literal:

```js
const target = {
  a: 1,
  b: 2,
  c: 3,
  sum: function() { return this.a + this.b + this.c; }
};
```

You can examine and update the numeric properties and sum their values:

```js
console.log( target.sum() );  // 6

target.a = 10;
console.log( target.a );      // 10
console.log( target.sum() );  // 15
```

JavaScript is a forgiving language and it lets you make invalid updates which could cause problems later:

```js
target.a = 'not a number!';
delete target.b;
target.c = undefined;
target.d = 'new property';

target.sum = () => 'hello!';

console.log( target.sum() );  // hello!
```

Note: you can prevent some unwanted actions using `Object` methods such as [`.defineProperty()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), [`.preventExtensions()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions), and [`.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) but they're blunt tools and won't prevent all updates.

A proxy object can intercept changes to a **target** object. It's defined with a **handler** that sets **trap** functions called when certain actions occur (get, set, delete, etc.) to change the behavior of the **target** object.

The following handler intercepts all `set` property operations such as `myObject.a = 999`. It's passed the `target` object, the `property` name as a string, and the `value` to set:

```js
const handler = {

  // set property
  set(target, property, value) {

    // is value numeric?
    if (typeof value !== 'number' || isNaN(value)) {
      throw new TypeError(`Invalid value ${ value }`);
    }

    return Reflect.set(...arguments);

  }

}
```

The function throws an error when the passed `value` is `Nan` or not numeric. If it's valid, [`Reflect`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Reflect) executes the default object behavior -- in this case to [`set`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set) the target object's property (all `Reflect` method parameters are identical to the proxy's handler function). You could use `target[property] = value;` instead but `Reflect` makes some operations easier to manage.

You can now create a new `Proxy` object by passing the `target` and `handler` objects to its constructor:

```js
const proxy = new Proxy(target, handler);
```

Now use the `proxy` object instead of `target` -- the same properties and methods work as before:

```js
console.log( proxy.sum() );  // 6

proxy.a = 10;
console.log( proxy.a );      // 10
console.log( proxy.sum() );  // 15
```

But setting a non-numeric value raises a `TypeError` and the program halts:

```js
proxy.a = 'xxx'; // TypeError: Invalid value xxx
```

The following code improves the Proxy `handler` further:

* the `set` trap adds another check to ensure a property already exists and is numeric. It throws a `ReferenceError` when calling code attempts to set unsupported property names (anything other than `a`, `b`, or `c`) or override the `sum()` function.
    
* a new `deleteProperty` trap throws a `ReferenceError` when calling code attempts to delete any property, e.g. `delete proxy.a`.
    
* a new `get` trap throws a `ReferenceError` when calling code attempts to get a property or call a method which doesn't exist.
    

```js
const handler = {

  // set property
  set(target, property, value) {

    // is a valid property?
    if (!Reflect.has(target, property) || typeof Reflect.get(target, property) !== 'number') {
      throw new ReferenceError(`Invalid property ${ property }`);
    }

    // is value numeric?
    if (typeof value !== 'number' || isNaN(value)) {
      throw new TypeError(`Invalid value ${ value }`);
    }

    return Reflect.set(...arguments);

  },

  // delete property
  deleteProperty(target, property) {
    throw new ReferenceError(`Cannot delete ${ property }`);
  },

  // get property
  get(target, property) {

    // is a valid property?
    if (!Reflect.has(target, property)) {
      throw new ReferenceError(`Invalid property ${ property }`);
    }

    return Reflect.get(...arguments);

  }

}
```

Examples:

```js
proxy.a = 10;               // successful
proxy.a = null;             // TypeError: Invalid value null
proxy.d = 99;               // ReferenceError: Invalid property d
proxy.sum = () => 'hello!'; // ReferenceError: Invalid property sum
delete proxy.a;             // ReferenceError: Cannot delete a
console.log( proxy.e );     // ReferenceError: Invalid property e
```

Validating types is not the most interesting use of proxies and, should you require type support, perhaps you should consider [TypeScript](https://www.typescriptlang.org/)! We'll examine a more advanced example below.

## Proxy trap types

A `Proxy` allows you to intercept actions on a `target` object. The **handler** object defines **trap** functions. In most cases, you'll be using `get` or `set` but the following traps support more advanced use:

* [`construct(target, argList)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct)
    
    Called when creating an object with the `new` operator.
    
* [`get(target, property)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get)
    
    Called when examining a property or running a method.
    
* [`set(target, property, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set)
    
    Called when setting a property with a value. Returning `false` throws a `TypeError` exception.
    
* [`defineProperty(target, property, descriptor)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty)
    
    Called when using [`Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) to create or update a property. It must return `true` (successfully defined) or `false` (could not be defined).
    
* [`deleteProperty(target, property)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty)
    
    Called when deleting a property. It must return either `true` (deleted) or `false` (not deleted).
    
* [`apply(target, thisArg, argList)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply)
    
    Called when executing a **target** object which is a function (it's not called for functions defined as object methods).
    
* [`has(target, property)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys)
    
    Called when using a static method such as `in`, e.g. `'p' in object`. It must return either `true` (defined) or `false` (not defined).
    
* [`ownKeys(target)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys)
    
    Called when using [`Object.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys). It must return an array of enumerable string-keyed property names, such as `["a", "b", "c"]`.
    
* [`isExtensible(target)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/isExtensible)
    
    Called when using [`Object.isExtensible()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) to check whether the object permits new properties. It must return `true` or `false`.
    
* [`preventExtensions(target)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/preventExtensions)
    
    Called when using [`Object.preventExtensions()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) to stop the object permitting new properties. It must return `true` or `false`.
    
* [`getOwnPropertyDescriptor(target, property)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getOwnPropertyDescriptor)
    
    Called when using [`Object.getOwnPropertyDescriptor()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) to returns an object describing the configuration of a specific property. It must return an appropriate object with `value`, `writable`, `configurable`, `enumerable`, `get`, and `set` properties.
    
* [`getPrototypeOf(target)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getPrototypeOf)
    
    Called when getting the object prototype using [`Object.getPrototypeOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf).
    
* [`setPrototypeOf(target, prototype)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/setPrototypeOf)
    
    Called when setting the object prototype using [`Object.setPrototypeOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf).
    

All traps have an associated [`Reflect()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect) method with identical parameters so it's not necessary to create your own implementation code when you require the default behavior. For example:

```js
const handler = {

  // trap property descriptor
  getOwnPropertyDescriptor(target, property) {

    console.log(`examining property ${ property }`);

    return Reflect.getOwnPropertyDescriptor(...arguments);

  }

};
```

## Two-way data binding with a Proxy

Data binding synchronizes two or more disconnected objects. In this example, updating a form field changes a JavaScript object's property and vice versa.

[**View the demonstration Codepen...**](https://codepen.io/craigbuckler/pen/eYLaBGr?editors=1011)

*(Click the SUBMIT button to view the object's properties in the Codepen console.)*

The HTML has a form with the ID `myform` and three fields:

```html
<form id="myform" action="get">

  <input name="name" type="text" />
  <input name="email" type="email" />
  <textarea name="comments"></textarea>

</form>
```

The JavaScript code creates a `myForm` object bound to this form by passing its node to a `FormBinder()` factory function:

```js
// form reference
const myformElement = document.getElementById('myform');

// create 2-way data form
const myForm = FormBinder(myformElement);
```

From that point onwards, the object's properties return the current state of the associated field:

```js
myForm.name;      // value of name field
myForm.email;     // value of email field
myForm.comments;  // value of comments field
```

You can update the same properties and the associated form field will change accordingly:

```js
myForm.name = 'Some Name'; // update name field
```

![live field update](https://i.imgur.com/VoPtf0U.png align="center")

The implementation defines a `FormBind` class. The constructor examines all field elements in the passed form and, if they have a `name` attribute, it sets a property of that name to the field's current value. A private `#Field` object also stores the field's name and DOM node for later use.

```js
// form binding class
class FormBind {

  #Field = {};

  constructor(form) {

    // initialize object properties
    const elements = form.elements;
    for (let f = 1; f < elements.length; f++) {

      const field = elements[f], name = field.name;
      if (name) {
        this[name] = field.value;
        this.#Field[name] = field;
      }

    }
```

An `input` event handler triggers whenever a form field changes. It checks whether the `#Field` reference exists and updates the associated property with the field's current value.

```js
    // form change events
    form.addEventListener('input', e => {

      const name = e.target.name;
      if (this.#Field[ name ]) {
        this[ name ] = this.#Field[ name ].value;
      }

    });
```

An `updateValue()` method is then defined which updates both the object property and the HTML field when passed a valid `property` and `newValue`:

```js
  // update property and field
  updateValue(property, newValue) {

    if (this.#Field[ property ]) {

      this[property] = newValue;
      this.#Field[property].value = newValue;
      return true;

    }

    return false;

  }

}
```

To call this method, a Proxy handler defines a single `set` trap which intercepts a property update:

```js
// form proxy traps
const FormProxy = {

  // intercept set
  set(target, property, newValue) {
    return target.updateValue(property, newValue);
  }

};
```

A proxy factory function then provides an easy way to create an object which is bound an HTML form:

```js
// form 2-way data binder
function FormBinder(form) {
  return form ? new Proxy(new FormBind(form), FormProxy) : undefined;
}


// form node
const myformElement = document.getElementById('myform');

// create 2-way data form
const myForm = FormBinder(myformElement);

myForm.name = "Some Name";
```

While this is not production-level code, it illustrates the usefulness of JavaScript Proxies. If you want to develop it further, you can add further code to handle:

* unusual field names which would not be valid property names, such as `my-name` or [`my.name`](http://my.name)
    
* checkbox, radio, and select fields, and
    
* dynamic HTML DOM updates which add or remove form fields.
    

## Proxy power

Proxy support is available in all modern browsers and JavaScript runtimes including Node.js and Deno. They'll only be a problem if you have to support Internet Explorer 11 since there's no way to polyfill or transpile ES6 proxy code to ES5.

Proxies won't be necessary in all your applications but they provide some interesting opportunities for metaprogramming. You can write programs which analyze or transform other programs or even modify themselves while executing.
