JavaScript Proxy

A JavaScript Proxy serves as an advanced mechanism that enables you to intercept and modify the operations performed on objects. Introduced as part of ECMAScript 6 (ES6), proxies provide a means to enhance standard functionalities by allowing you to read, write, or delete an object's properties. Proxies can be employed for various purposes, including tracking activities, enforcing rules, conducting validations, and even altering behaviors dynamically.

What is a proxy?

In JavaScript, a proxy object serves to "enclose" another entity, referred to as the target, and intercepts operations performed on it. These operations are termed traps. Some common traps include get (which intercepts the reading of a property), set (which intercepts the writing of properties), and deleteProperty (which intercepts the deletion of properties).

Developers have the ability to "attract" these operations via a proxy, allowing them to execute custom logic prior to the operation arriving at its intended destination.

Syntax

Example

const proxy = new Proxy(target, handler);

When it comes to establishing a proxy, there are two essential components to consider:

  • Target: This refers to the object that you intend to encapsulate or proxy.
  • Handler: A handler is an object containing methods that define specialized behavior for particular actions.

Example

Example

const targetObject = {

  message: "Hello",

};

const handler = {

  get: function(target, property) {

    return `Intercepted: ${target[property]}`;

  }

};

const proxy = new Proxy(targetObject, handler);

console.log(proxy.message);  // Output: Intercepted: Hello

Output:

Output

Intercepted: Hello

Functions aimed at intercepting operations within the handler object are referred to as proxy traps. Some of the most commonly utilized traps include:

The retrieval of assets from the target item is facilitated by the get lure.

Example

const handler = {

  get: function(target, property) {

    if (property in target) {

      return target[property];

    } else {

      return `Property "${property}" not found.`;

    }

  }

};

const proxy = new Proxy({}, handler);

console.log(proxy.message);  // Output: Property "message" not found.

Output:

Output

Property "message" not found.

Communicating with a property regarding the objective is obstructed by the established trap.

Example

const handler = {

  set: function(target, property, value) {

    if (typeof value === 'number') {

      target[property] = value;

      return true;

    } else {

      throw new TypeError("Value must be a number");

    }

  }

};

const proxy = new Proxy({}, handler);

proxy.age = 25;  // Works fine

proxy.name = "John";  // Throws: Value must be a number

deleteProperty

Efforts to remove properties are intercepted through the deleteProperty trap.

Example

const handler = {

  deleteProperty: function(target, property) {

    if (property in target) {

      delete target[property];

      return true;

    } else {

      console.warn(`Property "${property}" doesn't exist`);

      return false;

    }

  }

};

const target = { name: "Alice" };

const proxy = new Proxy(target, handler);

delete proxy.name;  // Works

delete proxy.age;   // Logs: Property "age" doesn't exist

Output:

Output

Property "age" doesn't exist

The in-operator is intercepted via the has lure.

Example

const handler = {

  has: function(target, property) {

    return property in target && target[property] !== null;

  }

};

const target = { name: "Alice", age: null };

const proxy = new Proxy(target, handler);

console.log("name" in proxy); // true

console.log("age" in proxy);  // false

Output:

Output

true

false

Use Cases of Proxies

Under certain circumstances, proxies can prove to be especially beneficial:

Verification

Proxies can confirm the accuracy of information in a task when you intend to enforce specific criteria, such as testing types or mandatory fields.

Example

const validator = {

  set: function(target, property, value) {

    if (property === "age") {

      if (typeof value !== "number" || value <= 0) {

        throw new Error("Age must be a positive number");

      }

    }

    target[property] = value;

    return true;

  }

};

const person = new Proxy({}, validator);

person.age = 25; // Valid

person.age = -5; // Error: Age must be a positive number

Access Logging

To register assets, obtain an entry for the purpose of monitoring or troubleshooting:

Example

const logHandler = {

  get: function(target, property) {

    console.log(`Property ${property} accessed`);

    return target[property];

  }

};

const data = { name: "Alice", age: 25 };

const proxy = new Proxy(data, logHandler);

console.log(proxy.name);  // Logs: Property name accessed

Output:

Output

Property name accessed

Alice

Standard Values

You have the option to provide fallback values to guarantee specific defaults:

Example

const defaultHandler = {

  get: function(target, property) {

    return property in target ? target[property]: "Default Value";

  }

};

const settings = { theme: "dark" };

const proxy = new Proxy(settings, defaultHandler);

console.log(proxy.theme);       // Output: dark

console.log(proxy.language);    // Output: Default Value

Output:

Output

dark

Default Value

Arrays with Negative Indexing

To facilitate suboptimal indexing for arrays, one might consider utilizing proxies:

Example

const arrayHandler = {

  get: function(target, property) {

    if (typeof property === 'string' && property < 0) {

      return target[target.length + Number(property)];

    }

    return target[property];

  }

};

const arr = [1, 2, 3];

const proxy = new Proxy(arr, arrayHandler);

console.log(proxy[-1]);  // Output: 3 (last item)

Output:

Reflect API with Proxies

Moreover, JavaScript introduces the Reflect API, which enhances proxies by offering a mechanism to execute default actions that proxies intercept. This feature allows you to call upon the default behavior of the trap.

Example

const handler = {

  set: function(target, property, value) {

    console.log(`Setting ${property} to ${value}`);

    return Reflect.set(target, property, value);

  }

};

const obj = {};

const proxy = new Proxy(obj, handler);

proxy.name = "Alice";  // Logs: Setting name to Alice

Output:

Output

Setting name to Alice

Real-world Applications of Proxies

Reactive Data Binding

Applications of Proxies in Real-World Scenarios

6.1 Reactive Data Binding

In frameworks like Vue.js, reactivity is implemented through the use of proxies. You can also utilize the get and set traps to automatically refresh the user interface and monitor any modifications to the properties.

Interception of API Requests

By utilizing proxies to intercept calls to API objects, you can incorporate custom functionalities, such as caching responses, handling errors, or transforming the request format.

Example

const api = {

  getUser: (id) => fetch(`/api/user/${id}`).then(response => response.json())

};

const cacheHandler = {

  get: function(target, property) {

    if (!target.cache) {

      target.cache = {};

    }

    if (property in target.cache) {

      return Promise.resolve(target.cache[property]);

    } else {

      return target[property]().then((result) => {

        target.cache[property] = result;

        return result;

      });

    }

  }

};

const cachedApi = new Proxy(api, cacheHandler);

Limitations of Proxies in JavaScript

Performance: The incorporation of proxies adds an additional layer of abstraction, which has the potential to lead to delays in execution. When utilizing them in sections of your code that are critical to overall performance, it is advisable to proceed with caution.

Incompatibility with JSON.Stringify: Objects that are Proxies cannot be processed by JSON.Stringify, which might lead to their omission or trigger an error under certain circumstances.

No Polyfill: Due to the reliance of proxies on specific language functions that JavaScript cannot fully replicate, it is not possible to create polyfills for them in modern browsers.

JavaScript proxies serve as a robust tool that enables developers to modify and oversee fundamental object interactions. They prove to be highly beneficial in scenarios such as access control manipulation, logging, validation, and even in data binding frameworks. Nevertheless, it is essential to utilize them with caution due to their complexity and potential impact on performance.

Input Required

This code uses input(). Please provide values below: