The Proxy
object, introduced in ECMAScript 2015, allows us to intercept operations performed on objects. We can define custom behavior for these operations, allowing us to control exactly what occurs when one of these operations is performed. Let’s first look at how we might intercept calls to retrieve a property on an object.
In the example above, we are creating a new proxy object called proxyPlayer
that will act as an intermediary between the original player
object and the end user trying to access a property. The first parameter we provide when creating a new proxy object is the target object. This is the object that we want our proxy to wrap and intercept operations to. The second parameter we pass is a handler object with methods that define how we want to handle certain operations on this object. There are quite a few methods that this handler object supports, including property lookup, assignment, deletion, and enumeration. For a full list of supported handler methods, take a look at the proxy handler documentation on MDN.
In our example, we are passing an object literal with a get
property that will specify the behavior when an object property is trying to be retrieved. In this case, we would like to return a player’s score with the string ‘points’ appended to the end. We can achieve this by first checking if we are accessing the score
property and then returning a string that combines the value of that score property with the word ‘points’. It’s important to remember that we still need to return the normal value for all other object properties that we don’t want to affect. To account for these other object properties, we return the original value of all other object properties within the else
block of the if
statement.
Now if we retrieve the score property from the proxyPlayer object, we should see our new return value. No other object property lookups should be affected.
Notice that we must access these object properties through the proxy and not through the original object itself. If I were to bypass the proxy, operating directly on the player
object, I wouldn’t see this custom behavior.
Proxies also allow us to intercept function invocations. To demonstrate this, let’s add a method to our player
object.
Here, we’ve added a promote
method that increments the player’s rank and returns the new rank. Let’s say we only want to allow the player
object to invoke this method, preventing any other objects from calling the promote
function. To do this, we can create a proxy around the promote
function, intercepting any calls to the function.
In this example, player.promote
is now a proxy based on the original player.promote
function. The original player.promote
function is no longer accessible. Every call to the function now runs through this new proxy.
Within our new proxy, we are using the apply
handler method to intercept any calls to this function. The apply
handler method accepts three parameters: the target object, the context of the function invocation, and an array of arguments to the function. In our case, the target object is our promote
function itself. The context is the object represented by the this
keyword when the function is invoked. For example, if I call player.promote()
, the context would be the player
object.
Within the apply
method we are verifying that the context for the function invocation is the player
object before executing the function. If any other object tries to invoke the promote function, it will fail and return a string explaining that only the player object can use the promote
function.
Proxies provide developers with a powerful way of manipulating the behavior and functionality of certain operations. Whether you need to implement some sort of validation logic, or just want to extend the behavior of some default operation, proxies can be a valuable tool.