JS Tip of the Day: Proxy Objects

Proxy Objects
Version: ES2015
Level: Advanced

Proxy objects are exotic objects that transparently wrap other objects providing a method for intercepting operations like property access or assignment. In doing this, they can provide functionality that is not possible with normal JavaScript objects.

To create a Proxy object, you would use the Proxy constructor, passing in the object to be wrapped followed by a handler object which defines all of the operations that are to be intercepted. The definitions in the handler are known as traps.

let gift = { value: 'Holiday 6 Pack' };
let wrapped = new Proxy(gift, { /* add traps here */ });
console.log(wrapped.value); // Holiday 6 Pack

In the example above, wrapped is the proxy version of the gift object. Because the handler is empty ({}), having no traps, it will appear in every way like the original gift object.

In fact there’s no observable way to tell that a proxy object is not the original wrapped object unless you compared them to each other directly. Even checks like instanceof will continue to work as though acting on the original object because it goes through the proxy rather than acting on it.

let date = new Date();
let wrapped = new Proxy(date, {});
console.log(wrapped instanceof Date); // true
console.log(wrapped instanceof Proxy); // Error

By defining a trap in the handler we can add additional behavior to the wrapped object. A common trap is the get trap that is used to intercept property access. The can be used to change the result of what happens when you access the value property in the proxy version of the gift object.

let gift = { value: 'Holiday 6 Pack' };
let wrapped = new Proxy(gift, {
    get (target, property, receiver) {
        let value = Reflect.get(target, property, receiver);
        if (property === 'value') {
            return value.replace('6', '5'); // altered value
        }
        return value; // original value
    }
});
console.log(wrapped.value); // Holiday 5 Pack

Here, the original value is obtained through Reflect.get() (from the wrapped gift object) and if the name of that property is 'value', the result is altered changing instances of ‘6’ to ‘5’. Other properties behave normally returning the same value as they would if there were no proxy.

While the above behavior can also be achieved with a getter in an accessor property, what accessors can’t do that proxies can is intercept properties that don’t yet exist. With of this, you could create an array-like behavior that automatically updates a length property when new indexed values are set - something which would not be possible with accessor properties alone.

let fakeArray = new Proxy({ length: 0 }, {
    set (target, property, value, receiver) {
        let newLength = Number(property) + 1;
        let length = Reflect.get(target, 'length', receiver);
        if (newLength > length) {
            Reflect.set(target, 'length', newLength, receiver);
        }
        return Reflect.set(target, property, value, receiver);
    }
});
console.log(fakeArray.length); // 0
fakeArray[0] = 'a';
console.log(fakeArray.length); // 1
fakeArray[4] = 'e';
console.log(fakeArray.length); // 5

Traps exist for many other operations include property defining (not just assigning), function calling, getting object keys, and many more. See the MDN link below for the additional traps you can define for a proxy.

More info: