JavaScript Jems - The Proxy
Written by Mike James   
Monday, 12 April 2021
Article Index
JavaScript Jems - The Proxy
Proxy Traps

The proxy is a mysterious object that lurks behind other objects - the question is why? This is an extract from my newly published book, JavaScript Jems: The Amazing Parts.

Now available as a book from your local Amazon.

JavaScript Jems:
The Amazing Parts

kindlecover

Contents

<ASIN:1871962579>

<ASIN:1871962560>

<ASIN:1871962501>

<ASIN:1871962528>

Jem 19 Metaprogramming - The Proxy

A nice state of affairs when a man has to indulge his vices by proxy.”

Raymond Chandler

Metaprogramming is strictly defined as being where code can be treated as data. JavaScript is a natural at metaprogramming because it can treat all code as data, even if this is generally frowned upon because it has many potential dangers - see Jem 10: Code as Data. As well as code being treated as data, metaprogramming also tends to mean doing things that change the way the language works and the new proxy object introduced in ES2015 arguably does this.

Whatever you want to call it, the Proxy object brings some much-needed capabilities to JavaScript, making it a jem in it’s own right.

The Proxy Object

So what is a proxy? Put simply, a proxy is something that stands in for something else. In this case you can define a proxy for any JavaScript object and you can select what types of thing it will stand in for.

You create a Proxy object in the usual way:

const myProxy=new Proxy(target,handler);

where target is the object that the new object will proxy for. The second parameter is an object that defines the actions that the proxy will deal with and functions that define what happens. Any actions that are not defined in the handler are passed on to the target.

You can think of the Proxy as wrapping the target object and receiving a range of requests that would normally be handled automatically and allowing you to customize them. For example, you can use the get handler function to intercept any property retrieval. That is, the get function is called whenever a property of the target object is read and it can return a value for the property irrespective of what is actually stored in the target's property or even if the target has the property at all:

let myObject={myProperty:0}; 
const handler={get:function(target,prop,receiver){
                      return 42; 
                   }
        };
myObject = new Proxy(myObject, handler);

The parameters of the get function provide it with the target object, the property name and the receiver of the request - usually the Proxy object itself. You can see in this example that the get function returns 42, no matter what the property name is.

So if you try:

console.log(myObject.myProperty); 
console.log(myObject.noProperty);

you will see 42 displayed for both properties, even though the first is set to 0 and the second doesn't actually exist.

In this case we wrapped the original object in a Proxy and lost the reference to it. This means we can only access the object via the proxy. However, if we had kept a reference to the original object:

myObject2 = new Proxy(myObject, handler);

then using myObject2 would go via the proxy and myObject would use the properties of the original object without the interference of the proxy. That is, references to the proxy are intercepted, but references to the original object aren't. You can also revoke a Proxy object and after a revoke you can only access the original object.

Notice that this really is metaprogramming because you are modifying the default way that a property is accessed.

An alternative way of creating an object wrapped by a Proxy is to wrap a null object and then add properties:

const myObject=new Proxy({},handler); 
myObject.myProperty=0;

This is a good approach to use in a constructor or an object factory.

Proxy objects also work within the prototype chain, for example:

const handler={get:function(target,prop, receiver){
                      return 42; 
                   }
               };
let myProxy=new Proxy({},handler);
const myObject=Object.create(myProxy,
{myProperty:{value:0}});
console.log(myObject.myProperty); console.log(myObject.noProperty);

In this case we create a proxy wrapping a null object and then create myObject with the proxy as its prototype. Now any properties defined on myObject are own properties and work as normal, i.e. myProxy does not intercept any uses of them, but any properties that are not own properties are searched for on the prototype chain and myProxy is invoked. As a result you see 0 displayed for myProperty and 42 for noProperty. In this case the receiver parameter is set to reference myObject rather than the wrapped null object.

You can also wrap built-in objects with a Proxy and this is very powerful.

Proxy Traps

Two of the easiest to understand proxy handlers, or traps as the documentation calls them, are get and set. We used get as the first example of using a Proxy in the previous section and set works in the same way, but intercepts any attempt to store something in a property:

get:function(target,prop, receiver)
set:function(target,prop, value, receiver)

Notice that both functions have the name of the property, prop and set also has the value being stored. The set function also has to return true if the value is written successfully and false otherwise. Also notice that unlike simple get and set accessor functions, proxy get and set intercept accesses to all properties. What this means is that you can use get and set to do things that are different for each property - tasks like validating, sanitizing and formatting data. You only need use the Proxy get and set if you need to modify the way a set or all the properties of an object are to behave i.e. a central approach to processing all properties.

So what sorts of things need Proxy get and set?



Last Updated ( Monday, 12 April 2021 )