logo
Published on

Understanding JavaScript Proxy Objects

Authors
  • avatar
    Name
    Nadir Tellai

Mastering JavaScript: Understanding Proxy Objects

Introduction

JavaScript is a language that never ceases to evolve, continually bringing new features and capabilities to enhance web development. The ES6 update, in particular, introduced a lot of new features, JavaScript Proxy objects despite being less popular are one of them. In this blog, we will explore what JavaScript Proxy objects are and how they are used.

Definition

A Proxy object is a wrapper around another object, it is used to intercept operations, like reading/writing properties and others. They provide a powerful mechanism for meta-programming and creating advanced abstractions over objects

Proxy objects are commonly used to log property accesses, validate, format, or sanitize inputs, and so on.

It was introduced to javascript in ES6, and it is supported by all major browsers.

Proxies are used in many libraries and frameworks such as Vue.js, Redux Toolkit and immer.

Syntax

let proxy = new Proxy(target, handler)
  • target: the original object which you want to proxy.
  • handler: proxy configuration: an object with “traps”, that defines which operations will be intercepted and how to redefine intercepted operations.
  • trap: The function that defines the behavior for the corresponding object internal method.

Examples

1. Get trap:

To intercept reading/ accessing the target properties, the handler must have a function get as a property.

A common use case for this trap is defining object default value.

let user = { name: "John", age: 25  };

user = new Proxy(user, {
  get(target, prop) {
    if ( target[prop] !== undefined) {
      return target[prop];
    } else {
      return "unknown"; // default value
    }
  }
});


console.log(user.age) // 25
console.log(user.address) // "unknown"

Notice how we have replaced the user value with the proxy, you don't want the target object to be referenced after it got proxied.

2. Set Trap:

This trap intercepts property assignments. It's useful for validating or modifying the value before it's assigned to the property.

let user = { name: 'Alice' };

user = new Proxy(user, {
  set(target, prop, value) {
    if (prop === 'age' && typeof value !== 'number') {
      throw new Error('Age must be a number.');
    }
    target[prop] = value;
    return true; // required to indicate success
  }
});

user.age = 30; // Works fine
user.age = 'thirty'; // Throws an error

3. Has Trap:

This trap intercepts the use of the in operator. It's useful for hiding certain properties from in checks.

Example:

let hiddenProperties = ['password'];
let user = { name: 'Bob', password: '123'};

user = new Proxy(user, {
  has(target, prop) {
    if (hiddenProperties.includes(prop)) {
      return false;
    }
    return prop in target;
  }
});

console.log('password' in user); // false
console.log('name' in user); // true

4. DeleteProperty Trap:

This trap intercepts property deletions using the delete operator. It can prevent deletion of certain properties.

Example:

let user = { id: 1, name: 'Charlie', age: 40 };

user = new Proxy(user, {
  deleteProperty(target, prop) {
    if (prop === 'id') {
      console.log('Cannot delete id');
      return false; // required to prevent deletion
    }
    delete target[prop];
    return true;
  }
});

delete user.id; // "Cannot delete id"
delete user.age; // Works

5. OwnKeys Trap:

This trap intercepts operations that request a list of object property names, like Object.keys() and for...in loops.

Example:

let user = { name: 'Dave', age: 35, _id: 'user123' };

user = new Proxy(user, {
  ownKeys(target) {
    return Reflect.ownKeys(target).filter(key => !key.startsWith('_'));
  }
});

console.log(Object.keys(user)); // ["name", "age"]

Reflect object allows us to call operators like ownKeys, new, delete… as functions.

6. Apply Trap:

This trap is used for function calls. It allows you to intercept and customize behavior when a Proxy for a function is invoked.

Example:

function sum(a, b) {
  return a + b;
}

let proxySum = new Proxy(sum, {
  apply(target, thisArg, argumentsList) {
    console.log(`Computing the sum of ${argumentsList}`);
    return Reflect.apply(target, thisArg, argumentsList);
  }
});

console.log(proxySum(1, 2)); // prints "Computing the sum of 1,2" then prints 3.

These examples demonstrate how Proxy traps in JavaScript can be used for various purposes

To get a complete understanding of all the available traps, I recommend diving deeper into the official documentation MDN Web Docs on Proxy.

Introduction of Proxy in Vue 3

If you have used vue 3, you must have encountered Proxy when you tried to print ref object to the console.

Vue 3, officially released in September 2020, embraced the ES6 Proxy feature to reimagine its reactivity system. This was a major step forward from Vue 2's reactivity model, which relied heavily on Object.defineProperty.

In Vue 2, the reactivity system couldn't detect property addition or deletion dynamically. You had to use Vue.set and Vue.delete to ensure reactivity for newly added or deleted properties. And also it could not detect changes when directly setting an item in an array with the index.

Both of these cases are no longer a limitation in Vue 3 thanks to proxies.

Conclusion

In this blog, we've learned how JavaScript Proxies work and how to benefit from their dynamic and powerful approach to managing object interactions by exploring some of their many use cases.