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.
In this article
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, 42TheProxy object defines atarget (an empty object here) and ahandler object, in which agettrap 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
TypeErrorwill 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 trapReflection
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"); // trueA 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}