(sies)
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
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:
Object.keys();
function barify (object, key) {
object[key] = 'bar';
}
function doMaths (a, b, c) {
return a * b + c;
}
doMaths.length; // 3
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.
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'
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?'
If the following invariants are violated, the proxy will throw a TypeError:
A trap may have a set of invariants, which are rules around behaviour that cannot be modified.
For example, the deleteProperty trap:
and more...
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');
Proxies are powerful, but they aren't omnipotent
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 😢
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 😢