Understanding Proxies in JavaScript

Understanding Proxies in JavaScript

An overview of what a Proxy is, how it works, and why it exists.

·

5 min read

If you're looking to dive in to some more advanced JavaScript concepts, the Proxy API is a great place to start! Proxies give you the ability to control how properties are accessed on objects, including getting, setting, and deleting values. In addition, you can use proxies to implement custom behavior for common operations like iteration and assignment.

Proxies are a relatively new feature in JavaScript, and provides a way to create "virtual" objects that can intercept property accesses and perform custom operations.

What are Proxies?

A Proxy is like a middle-man who can sit between you and the thing you're trying to access. You can tell the Proxy what you want to do, and it can either do it for you, stop you from doing it, or do something else entirely.

In JavaScript, objects can have properties that are themselves objects. For example, you might have an object with a property named "foo" that contains another object:

var obj = {
    foo: {
        // ...
    }
};

Proxies provide a way to intercept these property accesses and perform custom operations. In the above example, we could use a proxy to intercept calls to obj.foo and do something else instead:

const proxy = new Proxy(obj, {
    get: function(target, property, receiver) {
        if (property == "foo") {
            // ... do something else
        } else {
            // return the default behavior
            return Reflect.get(target, property, receiver);
        }
    }
});

In this case, we intercept calls to get (which is used to retrieve a property value) and check if the property name is "foo". If it is, we do something else; otherwise, we return the default behavior.

Creating Proxies

Proxies are created using the new Proxy() constructor:

const proxy = new Proxy(target, handler);

The target argument is the object that the proxy will "proxy" for; that is, operations on the proxy will be forwarded to the target object. The handler argument is an object that defines which operations the proxy will intercept.

Proxy Handler Methods

The handler object can define any of the following methods:

  • get(target, property, receiver): Called when a property is accessed.
  • set(target, property, value, receiver): Called when a property is set.
  • has(target, property): Called when using the in operator.
  • deleteProperty(target, property): Called when using the delete operator.
  • apply(target, thisArg, argumentsList): Called when a function is invoked.
  • construct(target, argumentsList, newTarget): Called when a constructor is invoked.

These methods all have the same signature as the corresponding Reflect methods. The Reflect methods are used to perform the default behavior for an operation; if you want to customize the behavior, you can override the corresponding handler method.

Example: Validating Property Values

In these examples, the JavaScript Proxy API acts like a security guard at a nightclub. It can intercept requests and make sure they are valid before allowing them to pass through.

Let's say we have an object that represents a user's profile, and we want to make sure that the age property is always a positive number. We can do this by creating a proxy for the object, and intercepting calls to set:

const profile = {
    name: "John Doe",
    age: 42
};

const proxy = new Proxy(profile, {
    set: function(target, property, value, receiver) {
        if (property == "age") {
            if (typeof value != "number" || value < 0) {
                throw new Error("Invalid age");
            }
        }
        // return the default behavior
        return Reflect.set(target, property, value, receiver);
    }
});

Now, if we try to set the age property to an invalid value, we'll get an error:

proxy.age = "42"; // Error: Invalid age

Example: Logging Property Accesses

In this example, we'll create a proxy that logs all property accesses:

const obj = {
    foo: "bar"
};

const proxy = new Proxy(obj, {
    get: function(target, property, receiver) {
        console.log("getting property " + property);
        // return the default behavior
        return Reflect.get(target, property, receiver);
    },
    set: function(target, property, value, receiver) {
        console.log("setting property " + property + " to " + value);
        // return the default behavior
        return Reflect.set(target, property, value, receiver);
    }
});

Now, whenever we access or set a property on proxy, we'll see a message in the console:

proxy.foo; // getting property foo
proxy.foo = "baz"; // setting property foo to baz

Proxy Revocability

Proxies are revocable, which means that they can be disabled after they've been created. To do this, we use the Proxy.revocable() method:

const obj = {
    foo: "bar"
};

const revocable = Proxy.revocable(obj, {
    get: function(target, property, receiver) {
        console.log("getting property " + property);
        // return the default behavior
        return Reflect.get(target, property, receiver);
    },
});

const proxy = revocable.proxy;

Now, proxy behaves just like our previous example. We can access and set properties, and we'll see messages in the console:

proxy.foo; // getting property foo
proxy.foo = "baz"; // setting property foo to baz

When we're finished with the proxy, we can disable it by calling the revoke() method:

revocable.revoke();

Now, if we try to access or set properties on proxy, we'll get an error:

proxy.foo; // TypeError: Cannot perform 'get' on a proxy that has been revoked
proxy.foo = "baz"; // TypeError: Cannot perform 'set' on a proxy that has been revoked

Conclusion

Proxies provide a powerful way to intercept and customize property accesses in JavaScript. I hope this has helped you understand how to create proxies, and how to use them to perform operations such as validating property values and logging property accesses.

Related to Proxies is the incredibly useful Reflect API, so stay follow or signup for an article on that soon.

Let me know in the comments if you liked this or have any questions—be sure to follow me for more like this!

Did you find this article valuable?

Support trav by becoming a sponsor. Any amount is appreciated!