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

The most obvious application is to control what happens when you try to use a property that doesn't exist. By default if you try to retrieve a property that doesn't exist you get undefined. If you try to store a value in a property that doesn't exist then the property is created and the value stored. A more symmetric behavior would be to create the property when it is read with a default value. For example:

let myObject = {myProperty: 0};
const handler = {get: function (target, prop, receiver) {
                        if (target[prop] === undefined) {
                           target[prop]=null; 
                        }
                        return target[prop];
                      }
                };

In this case any property that doesn't exist, i.e. is undefined, is created and set to null. This means that if you try to get a value from a nonexistent property you get null and the property is created. If you try:

myObject=new Proxy(myObject,handler); 
console.log(myObject.hasOwnProperty("noProperty"));
console.log(myObject.noProperty);
console.log(myObject.hasOwnProperty("noProperty"));

you will see false, null, and true indicating that the property doesn't exist, has a default value of null and exists, respectively. Notice that within the handler the Proxy is bypassed and that only accesses from JavaScript code, not internal accesses, use the Proxy. That is, hasOwnProperty bypasses the Proxy.

Using proxy get and set you can create a replacement for the deprecated non-standard watch method. You can even go much further, for example:

let myObject = {myProperty: 0};
const handler = {get: function (target, prop, receiver) { 
              console.log(prop+" read "+target[prop]);
                     return target[prop];
                    },
           set: function(target,prop,value,receiver){ 
                    target[prop]=value;
                    console.log(prop+" write "+value);
                    return true; 
                   }
               };
myObject=new Proxy(myObject,handler); 

This will display messages whenever a property is accessed. For example:

let a=myObject.myProperty; 
myObject.myProperty=42;

results in:

myProperty read 0
myProperty write 42

kindlecover

You can also wrap built-in objects with a Proxy. For example, you can add negative index access to an array where myArray[-1] is the last element:

let myArray = [1, 2, 3, 4, 5, 6]; 
const handler = {get: function (target, prop, receiver) {
                 let index = +prop;
                 if (+prop < 0) { 
                   index = target.length + (+prop);
                 }
                 return target[index];
             }, 
        set: function (target, prop, value, receiver) {
                 let index = +prop; 
                 if (+prop < 0) { 
                   index = target.length + (+prop);
                 }
                 target[index] = value;
                 return true; 
          }
 };

Notice that the prop parameter is always a string and so in this case you have to convert it to a number using (+prop).

An advanced application of Proxy set and get is in creating objects which get their properties from remote server calls. For example, you could set up an object with properties that were fetched from a web server only when they were requested. In this case you would probably want to return a Promise rather than the property.

The Proxy get and set methods are more powerful than the usual accessor get and set methods but you should use the accessor functions where possible. If you simply want to intercept the access to a small number of properties use the accessor methods. If you need to intercept the access before the accessor methods would be called, or want to intercept all accesses, then use the Proxy get and set.

Other Handlers

As well as set and get the Proxy object can intercept a range of other object access modes:

Trap

 

getPrototypeOf()

A trap for Object.getPrototypeOf.

SetPrototypeOf()

A trap for Object.setPrototypeOf.

IsExtensible()

A trap for Object.isExtensible.

PreventExtensions()

A trap for Object.preventExtensions.

GetOwnPropertyDescriptor()

A trap for Object.getOwnPropertyDescriptor.

DefineProperty()

A trap for Object.defineProperty.

Has()

A trap for the in operator.

Get()

A trap for getting property values.

Set()

A trap for setting property values.

DeleteProperty()

A trap for the delete operator.

OwnKeys()

A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.

Apply()

A trap for a function call.

construct()

A trap for the new operator.

There are also constraints, called “invariants” in the documentation, that restrict what handlers can return, but these are mostly obvious. There is also a new Reflect object that has the same methods as Proxy, but implements the action on the specified object. For example:

let a=Reflect.get(target,prop);

returns the value of the property on the target. The Reflect object is mostly a tidying up of syntax involving Object.

While it is easy to understand what each of these do some of them present a problem in thinking up convincing use cases. Obviously, if you do want to customize the way that these internal JavaScript features behave, then this is the way to do it. Why you would want to change their behavior is more problematic. Some, however, do have immediate uses like get and set. Of the rest, the most interesting are apply and construct.

Included in book but not in this extract:

  • Proxy Apply
  • Proxy Construct
  • Wrapping Problems

Now available as a book from your local Amazon.

JavaScript Jems:
The Amazing Parts

kindlecover

Contents

<ASIN:1871962579>

<ASIN:1871962560>

<ASIN:1871962501>

<ASIN:1871962528>

espbook

 

Comments




or email your comment to: comments@i-programmer.info

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

 

 

 



Last Updated ( Monday, 12 April 2021 )