What are they?
Proxies give us greater control over what happens when we interact with objects. They wrap a target
object and
expose some of the internal operations on it. This means we can customise what happens when we get or set a
property, create a new instance of an object, call a function, and more (see the full list of the low-level
operations we can intercept
here).
Some layman definitions:
- Proxy: A wrapper object that exposes internal operations of a target object
- Target: The object the proxy is wrapping
- Handler: The proxy looks in this object for any trap methods you may have provided
- Trap: A function added to a handler object that tells the proxy what to do when a specific method on the target is called
Example
Here's an example of a proxy that adds logging for getting and setting a property on a target object.
// this is our target object
let friend = {
name: "Luna Lovegood",
house: "Ravenclaw",
patronus: "Hare"
};
// the handler for the proxy
let handler = {
// trap method
// this method will be called when we read a property on the proxy object
get(trapTarget, key, receiver) {
console.log(`Getting property "${key}"`);
// The Reflect object provides us with the default implementation
// There's a Reflect method for all proxy trap methods
return Reflect.get(trapTarget, key, receiver);
},
// trap method
// this method will be called when we set a property on the proxy object
set(trapTarget, key, value, receiver) {
console.log(`Setting property "${key}"`);
return Reflect.set(trapTarget, key, value, receiver);
}
};
// creating the proxy– we pass in the friend (target) and the handler
let friendProxy = new Proxy(friend, handler);
friendProxy.name;
// Getting property "name"
// 'Luna Lovegood'
friendProxy.hobby = "Painting";
// Setting property "hobby"
// 'Painting'
We used the get()
and set()
trap methods there to intercept calls that get and set properties on the target
object through the proxy. Since the proxy virtualises the target object, any operation available on the target
object can be called from the proxy.
Using proxies for validation
Proxies can be used to add validation to objects. I'll cover two use cases here– checking the arguments passed into a function match some validation criteria, and checking for typos in property names.
Parameter validation
Since there is an apply()
trap method available to us, we can add validation to make sure that the
arguments we pass into a function are what we would expect.
Let's create a speak()
function with the following requirements:
- We don't want to say more than one thing at a time
- We only want to speak in strings
// this is our target object
function speak(message) {
console.log(`Raising your head, you declare to the room "${message}"`);
}
let speakProxy = new Proxy(speak, {
// trap method
apply(trapTarget, thisArg, argumentsList) {
if (argumentsList.length !== 1) {
throw new Error("Please say one thing at a time");
}
if (typeof argumentsList[0] !== "string") {
throw new TypeError("Please use your words");
}
// call the apply method on the Reflect object to get the default
// behaviour
return Reflect.apply(trapTarget, thisArg, argumentsList);
}
});
speakProxy("Wingardium Leviosa");
// Raising your head, you declare to the room "Wingardium Leviosa"
speakProxy("Wingardium Leviosa", "Accio Firebolt!");
// Error: Please say one thing at a time
speakProxy(42); // TypeError: Please use your words
Here we have defined a speak()
method that prints out a given message.
Firstly, we added the apply()
trap method to the handler object. This method is executed when we call any
function on the proxy object.
Next, we added two conditions– the first checks that there is exactly 1 argument passed into
the function, the other checks that the type of the argument is a string. If we're happy that the argument
satisfies our validation criteria, then we call Reflect.apply()
to execute the default implementation.
Checking for typos in property names
When a property cannot be found on an object, the default behaviour is to return undefined
. This behaviour can
sometimes lead to hard to track down bugs, especially in larger in projects. We can guard against this using
proxies.
let friend = {
name: "Luna Lovegood",
house: "Ravenclaw",
patronus: "Hare"
};
friend.house; // Ravenclaw
friend.hoose; // undefined
let friendProxy = new Proxy(friend, {
get(trapTarget, key, receiver) {
if (!(key in receiver))
throw new TypeError("Property not recognised");
return Reflect.get(trapTarget, key, receiver);
}
});
friend.house; // Ravenclaw
friend.hoose; // undefined (the validation exists on the proxy, not the target)
friendProxy.house; // Ravenclaw
friendProxy.hoose; // TypeError: Property not recognised.
We can add similar validation to make sure we are only ever setting a property that already exists. This isn't always what we want, but it's still an interesting example:
let friend = {
name: "Andy Stabler",
house: "Hufflepuff",
patronus: undefined
};
friend.patronus = "Salmon";
friend.patroonus = "Stag";
friend;
// { name: 'Andy Stabler',
// house: 'Hufflepuff',
// patronus: 'Salmon',
// patroonus: 'Stag' }
// Whoopsy! We accidentally set a patroonus property!
let friendProxy = new Proxy(friend, {
get(trapTarget, key, receiver) {
if (!(key in receiver))
throw new TypeError("Property not recognised.");
return Reflect.get(trapTarget, key, receiver);
},
set(trapTarget, key, value, receiver) {
if (!(key in receiver))
throw new TypeError("Property not recognised.");
return Reflect.set(trapTarget, key, value, receiver);
}
});
delete friend.patroonus;
friendProxy.patronus = "Stag";
friendProxy.patroonus = "Wolf"; // TypeError: Property not recognised.
In both the get()
and set()
trap methods we performed a check to make sure that the key
was in
the receiver
object. The in
operator looks in the object and its prototype chain for a property with a matching key. If the
property is not found, then a TypeError
is raised.
Where can I learn more?
I've only scratched the surface of proxies and their many uses in ECMAScript. You can find out a lot more using the following resources:
- Understanding ECMAScript 6 is my favourite book right now. It has a very
thorough chapter on proxies which is worth reading.
- I'm making notes as I go through the book. Nicholas kindly let me make them public, so you can follow along here.
- The MDN web docs
- The ECMAScript specification