Meta programming
TheProxy
andReflect
objects allow you to intercept and define custom behavior for fundamental language operations (e.g., property lookup, assignment, enumeration, function invocation, etc.). With the help of these two objects you are able to program at the meta level of JavaScript.
Proxies
Proxy
objects allow you to intercept certain operations and to implement custom behaviors.
For example, getting a property on an object:
const handler = { get(target, name) { return name in target ? target[name] : 42; },};const p = new Proxy({}, handler);p.a = 1;console.log(p.a, p.b); // 1, 42
TheProxy
object defines atarget
(an empty object here) and ahandler
object, in which aget
trap is implemented. Here, an object that is proxied will not returnundefined
when getting undefined properties, but will instead return the number42
.
Additional examples are available on theProxy
reference page.
Terminology
The following terms are used when talking about the functionality of proxies.
- handler
Placeholder object which contains traps.
- traps
The methods that provide property access. (This is analogous to the concept oftraps in operating systems.)
- target
Object which the proxy virtualizes. It is often used as storage backend for the proxy. Invariants (semantics that remain unchanged) regarding object non-extensibility or non-configurable properties are verified against the target.
- invariants
Semantics that remain unchanged when implementing custom operations are calledinvariants. If you violate the invariants of a handler, a
TypeError
will be thrown.
Handlers and traps
The following table summarizes the available traps available toProxy
objects. See thereference pages for detailed explanations and examples.
RevocableProxy
TheProxy.revocable()
method is used to create a revocableProxy
object. This means that the proxy can be revoked via the functionrevoke
and switches the proxy off.
Afterwards, any operation on the proxy leads to aTypeError
.
const revocable = Proxy.revocable( {}, { get(target, name) { return `[[${name}]]`; }, },);const proxy = revocable.proxy;console.log(proxy.foo); // "[[foo]]"revocable.revoke();console.log(proxy.foo); // TypeError: Cannot perform 'get' on a proxy that has been revokedproxy.foo = 1; // TypeError: Cannot perform 'set' on a proxy that has been revokeddelete proxy.foo; // TypeError: Cannot perform 'deleteProperty' on a proxy that has been revokedconsole.log(typeof proxy); // "object", typeof doesn't trigger any trap
Reflection
Reflect
is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of theproxy handler's.
Reflect
is not a function object.
Reflect
helps with forwarding default operations from the handler to thetarget
.
WithReflect.has()
for example, you get thein
operator as a function:
Reflect.has(Object, "assign"); // true
A better apply() function
BeforeReflect
, you typically use theFunction.prototype.apply()
method to call a function with a giventhis
value andarguments
provided as an array (or anarray-like object).
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
WithReflect.apply
this becomes less verbose and easier to understand:
Reflect.apply(Math.floor, undefined, [1.75]);// 1Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]);// "hello"Reflect.apply(RegExp.prototype.exec, /ab/, ["confabulation"]).index;// 4Reflect.apply("".charAt, "ponies", [3]);// "i"
Checking if property definition has been successful
WithObject.defineProperty
, which returns an object if successful, or throws aTypeError
otherwise, you would use atry...catch
block to catch any error that occurred while defining a property. BecauseReflect.defineProperty()
returns a Boolean success status, you can just use anif...else
block here:
if (Reflect.defineProperty(target, property, attributes)) { // success} else { // failure}