Hi i'm Craig

I do JS AT

You can find me ON the interWebz:

PROXIES ROCK

(sies)

What Do I mean by proxy?

Not a web proxy

An ES2015 Proxy object

Spec?

Support?

Wat is it?

Proxies are a meta-programming feature that allow you to change how fundamental JS operations work on a target object.

 

By default, they don't do much:

let target = {};
let handlers = {};
let proxy = new Proxy(target, handlers);

console.log(proxy.foo); // undefined

Meta-programming?

function createFunction (name) {
    return `
        (function () {
            return function ${name} () {
                console.log('hello ${name}!');
                return createFunction('${name}');
            }
        })()
    `;
}

eval(createFunction('Craig'))();

Programs operate on data and data structures.

 

Meta-programs operate on programs:

Meta-programming

Object.keys();
function barify (object, key) {
    object[key] = 'bar';
}
function doMaths (a, b, c) {
    return a * b + c;
}
doMaths.length; // 3

A ProxY provides a mechanism for changing JS operations at runtime!

What does that look like?

let object = {};
let handlers = {};
let proxy = new Proxy(object, {
    get (target, key) {
        console.log(target, key);
    }
});

proxy.foo; // object, 'foo'

Here we have provided a trap for the "get" operation. Whenever a property is accessed on our object, the trap will fire.

What kind of traps are there?

get
set
has
deleteProperty
defineProperty
getOwnPropertyDescriptor
ownKeys
getPrototypeOf
setPrototypeOf
isExtensible
preventExtensions
set:
let manners = new Proxy({}, {
    set (target, key, value) {
        console.log(target, key, value);
        if (value.match(/please$/)) {
            Reflect.set(target, key, value);
        } else {
            console.log('nah');
        }
    }
});

manners.foo = 'bar'; // 'nah';
manners.foo = 'bar, please';
has:
let object = { foo: 'bar' };

let liar = new Proxy(object, {
    has (target, prop) {
        return !Reflect.has(target, prop);
    }
});

console.log('foo' in liar); // false;
console.log('baz' in liar); // true;

Note that in these examples we've used the Reflection API. Every Proxy trap has an equivalent method on the global Reflect object (also part of ES2015)

deleteProperty:
let object = { foo: 'bar' };

let hoarder = new Proxy(object, {
    deleteProperty (target, prop) {
        return false;
    }
});

delete hoarder.foo;
console.log(hoarder.foo); // 'bar'

Function only traps:

apply:
construct:
var func = function () { };
var trapped = new Proxy(func, {
    apply () {
        console.log('Hello?');
    },
    construct () {
        console.log('What do you want?');
    }
});

console.log(trapped()); // 'Hello?'
console.log(new trapped()); // 'What do you want?'

Invariants:

If the following invariants are violated, the proxy will throw a TypeError:

  • A property cannot be added, if the target object is not extensible.
  • A property cannot be added as or modified to be non-configurable, if it does not exists as a non-configurable own property of the target object.
  • A property may not be non-configurable, if a corresponding configurable property of the target object exists.
  • If a property has a corresponding target object property then Object.defineProperty(target, prop, descriptor) will not throw an exception.
  • In strict mode, a false return value from the defineProperty handler will throw a TypeError exception.

A trap may have a set of invariants, which are rules around behaviour that cannot be modified.

For example, the deleteProperty trap:

WHY?

Real use cases?

and more...

My use case:

let mock = ineeda();

// [[Get]] trap:
console.log(mock.foo.bar.baz); // Proxies all the way down.

// [[Apply]] trap:
console.log(mock.foo.bar.baz()); // throws Error('Not implemented');

// [[Set]] trap:
mock.value = 42;
console.log(mock.value) // 42;

// Whole bunch of traps:
sinon.stub(mock.some.deeply.nested, 'property');

What can't they do?

Proxies are powerful, but they aren't omnipotent

Missing traps?

function Point (x, y) {
    return new Proxy({ x, y }, {
        add (a, b) {
            return new Proxy(a.x + b.x, a.y + b.y);
        }
    });
}

var p1 = Point(1, 2);
var p2 = Point(3, 4);

console.log(p1 + p2); // { x: 4, y: 6 }
operator overloading:

Note: unfortunately, this isn't real JS 😢

Missing traps?

var object = {};

var falsy = new Proxy(object, {
    toBoolean (target) {
        return false;
    }
});

console.log(!!object); // true;
console.log(!!falsy); // false;
toBoolean:

Note: unfortunately, this isn't real JS 😢

FIN

Questions?

Proxies rocksies

By Craig Spence

Proxies rocksies

ES2015 Proxies

  • 3,528