JavaScript Proxy Objects

by Caolan McMahon

Get

food.name

Set

food.name = "Burrito";

Set

set(foo, "name", "Burrito");

Transpilers

Sorry, your browser does not support SVG.

Set

food.name = "Burrito";

Proxy Objects

  • Introduced in ECMAScript 2015 (ES6)
  • Can be used to override or intercept normal object behaviour
  • Even if you don't use it yourself, it's good to know what others might be capable of!

Creating a Proxy Object

new Proxy(target, handler);

Available Traps

  • has() - in operator
  • get() - getting property values
  • set() - setting property values
  • deleteProperty() - delete operator
  • apply() - function call
  • construct() - new operator

Available Traps

  • ownKeys()
  • getPrototypeOf()
  • setPrototypeOf()
  • isExtensible()
  • preventExtensions()
  • getOwnPropertyDescriptor()
  • defineProperty()

How might you use this?

  • Tracking mutation
  • Dynamic API clients
  • Python style array access
  • Operator overloading
  • Mock objects
  • Default values
  • Hidden properties
  • Property validation (e.g. database schema)
  • Lazy evaluation

Tracking Mutation

mutation.png

Tracking Mutation

function trackMutations(obj) {
    return new Proxy(obj, {
        set: function (target, property, value, receiver) {
            target[property] = value;
            if (property !== 'mutated') {
                target.mutated = true;
            }
            return true;
        }
    });
}
trackMutations() is available in the JS console

PokéAPI client

pikachu.gif

PokéAPI client

function Request(path) {
    return new Proxy(function () {}, {
        get: function (target, property, receiver) {
            return Request(path.concat([property]));
        },
        apply: function (target, thisArg, args) {
            var url = 'http://pokeapi.co/api/' + path.join('/');
            return fetch(url).then(function (res) {
                return res.json();
            });
        }
    });
}

window.pokeapi = Request([]);
The pokeapi object is available in the JS console.

Python-like Arrays

python.gif

Python-like Arrays

function PythonArray(arr) {
    return new Proxy(arr, {
        get: function (target, property, receiver) {
            if (property.indexOf(':') !== -1) {
                let [start, end] = property.split(':');
                start = Number(start);
                end = (end === '') ? undefined : Number(end);
                return target.slice(start, end);
            }
            return target[property];
        },
        has: function (target, property) {
            return target.indexOf(property) !== -1;
        }
    });
}
PythonArray() is available in the JS console.

Detecting a proxy

Proxies are shielded in two ways:

  1. It is impossible to determine whether an object is a proxy or not (transparent virtualization)
  2. You can’t access a handler via its proxy (handler encapsulation)

Detecting a proxy

But, here are some subtle ways you might modify your Proxy object to make it detectable.

Modify the prototype

function WrappedObject() {}

var data = {};
var proxy = new Proxy(data, {
    getPrototypeOf: function (target) {
        return WrappedObject.prototype;
    }
});
proxy instanceof WrappedObject // true
data instanceof WrappedObject  // false

Create hidden properties

var data = {
    name: 'Raccoon',
    likes: ['Trash']
};
var proxy = new Proxy(data, {
    get: function (target, property, receiver) {
        if (property === '_is_proxy') {
            return true;
        }
        return target[property];
    }
});
Object.keys(proxy) // ['name', 'likes']
proxy._is_proxy    // true
data._is_proxy     // undefined

Compatibility

compatibility-desktop.png http://kangax.github.io/compat-table/es6/#test-Proxy

Compatibility

compatibility-mobile.png

Compatibility

compatibility-servers.png

Compatibility

compatibility-compilers.png

Don't be scared

class Animal():
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name.upper() + "!"


print(Animal("Red Panda").name)

But do be cautious!

  • No backwards compatibility
  • Performance penalty over raw objects
  • Allows you to hijack seemingly innocent syntax wtf.gif

Links