JavaScript - JavaScript Proxies and the Reflect API

Introduction

JavaScript Proxies and the Reflect API are advanced features that allow developers to control and customize how objects and functions behave.

Normally, when you access an object property, assign a value, delete a property, or call a function, JavaScript performs the operation directly. With a Proxy, you can intercept these operations and execute your own logic before, after, or instead of the normal operation.

The Reflect API provides standard methods for performing these operations programmatically.

This topic is useful for understanding object validation, access control, logging, reactive systems, and advanced JavaScript frameworks.

1. What is a JavaScript Proxy?

A Proxy is an object that acts as a wrapper around another object or function. It allows you to intercept and customize operations performed on the original object.

The object being wrapped is called the target.

The Proxy uses a special object called a handler to define what happens when an operation is performed.

Basic syntax

JavaScript

const proxy = new Proxy(target, handler);

Where:

  • target: The original object or function that you want to wrap.

  • handler: An object containing methods called traps.

  • proxy: The new object through which operations are intercepted.

Example

JavaScript

const student = {
    name: "Rahul",
    age: 21
};

const handler = {
    get(target, property) {
        console.log("Property accessed:", property);
        return target[property];
    }
};

const proxyStudent = new Proxy(student, handler);

console.log(proxyStudent.name);

Output

Property accessed: name
Rahul

Explanation

  1. The student object contains name and age.

  2. A Proxy is created around the student object.

  3. The get() trap intercepts property access.

  4. When proxyStudent.name is executed, the get() method runs.

  5. The property value "Rahul" is returned.

The Proxy does not automatically modify the original object. It provides a controlled way to interact with it.

2. Why are Proxies useful?

Proxies are useful when you want to control or monitor object operations.

Common applications include:

Data validation

You can prevent invalid values from being assigned to an object.

For example, an employee's salary should not be negative.

Logging

You can record when a property is accessed, modified, or deleted.

Access control

You can restrict access to certain properties.

Reactive programming

You can detect changes to objects and trigger updates in an application.

Custom object behavior

You can change how objects respond to operations such as property access, assignment, and function calls.

3. What are Proxy Traps?

A Proxy trap is a special method inside the handler object that intercepts a particular JavaScript operation.

Each trap corresponds to an operation performed on the target.

For example:

|
Trap

|

Operation intercepted

|
| --- | --- |
|

get()

|

Reading a property

|
|

set()

|

Assigning a property

|
|

has()

|

Checking whether a property exists

|
|

deleteProperty()

|

Deleting a property

|
|

ownKeys()

|

Retrieving property keys

|
|

getOwnPropertyDescriptor()

|

Getting a property descriptor

|
|

defineProperty()

|

Defining a property

|
|

getPrototypeOf()

|

Getting the prototype

|
|

setPrototypeOf()

|

Setting the prototype

|
|

isExtensible()

|

Checking extensibility

|
|

preventExtensions()

|

Preventing extensions

|
|

apply()

|

Calling a function

|
|

construct()

|

Using a function with new

|

A handler does not need to define every trap. If a trap is missing, the operation generally behaves like the corresponding operation on the target.

4. The get() Trap

The get() trap is executed when a property is read through a Proxy.

Syntax

JavaScript

get(target, property, receiver) {
    // Custom logic
}

Parameters

  • target: The original object.

  • property: The property name being accessed.

  • receiver: The object on which the property access originated, usually the Proxy.

Example

JavaScript

const product = {
    name: "Laptop",
    price: 50000
};

const handler = {
    get(target, property) {
        console.log("Reading property:", property);
        return target[property];
    }
};

const proxyProduct = new Proxy(product, handler);

console.log(proxyProduct.name);
console.log(proxyProduct.price);

Output

Reading property: name
Laptop
Reading property: price
50000

Explanation

When proxyProduct.name is accessed, the Proxy invokes the get() trap.

The trap receives:

JavaScript

target = product
property = "name"

The expression:

JavaScript

return target[property];

returns the actual property value.

Important point

The get() trap runs when accessing properties through the Proxy, not when directly accessing the original object.

JavaScript

console.log(product.name);

This direct access does not use the Proxy's get() trap.

5. The set() Trap

The set() trap is used to intercept property assignments.

It runs when a value is assigned through a Proxy.

Syntax

JavaScript

set(target, property, value, receiver) {
    // Custom logic
}

Parameters

  • target: The original object.

  • property: The property being modified.

  • value: The new value.

  • receiver: The Proxy or object through which the assignment occurs.

Example: Logging assignments

JavaScript

const employee = {
    name: "Anita",
    salary: 30000
};

const handler = {
    set(target, property, value) {
        console.log("Updating:", property, "to", value);

        target[property] = value;

        return true;
    }
};

const proxyEmployee = new Proxy(employee, handler);

proxyEmployee.salary = 40000;

console.log(proxyEmployee.salary);

Output

Updating: salary to 40000
40000

Explanation

When this statement runs:

JavaScript

proxyEmployee.salary = 40000;

The set() trap is called.

The property is updated using:

JavaScript

target[property] = value;

The trap returns true to indicate that the assignment was successful.

Why must set() return true or false?

A set() trap must return a Boolean-compatible result.

  • true: The assignment is reported as successful.

  • false: The assignment is reported as unsuccessful.

In strict mode, returning false from a failed assignment can cause a TypeError.

6. Data Validation Using Proxy

One of the most practical uses of a Proxy is validating data before storing it.

Suppose an employee's salary must be a positive number.

Example

JavaScript

const employee = {
    name: "Ravi",
    salary: 30000
};

const handler = {
    set(target, property, value) {

        if (property === "salary") {

            if (typeof value !== "number" || value < 0) {
                throw new Error("Salary must be a positive number");
            }
        }

        target[property] = value;

        return true;
    }
};

const proxyEmployee = new Proxy(employee, handler);

proxyEmployee.salary = 45000;

console.log(proxyEmployee.salary);

Output

45000

If you try:

JavaScript

proxyEmployee.salary = -5000;

The following error is thrown:

Error: Salary must be a positive number

Explanation

  1. The assignment is intercepted by set().

  2. The trap checks whether the property is salary.

  3. It checks whether the new value is a number.

  4. It checks whether the value is less than zero.

  5. If invalid, an error is thrown.

  6. If valid, the value is assigned.

This allows validation logic to be centralized in one place.

7. The has() Trap

The has() trap intercepts the in operator.

Syntax

JavaScript

has(target, property) {
    // Custom logic
}

Example

JavaScript

const student = {
    name: "Priya",
    age: 22
};

const handler = {
    has(target, property) {
        console.log("Checking:", property);
        return property in target;
    }
};

const proxyStudent = new Proxy(student, handler);

console.log("name" in proxyStudent);
console.log("marks" in proxyStudent);

Output

Checking: name
true
Checking: marks
false

Explanation

The expression:

JavaScript

"name" in proxyStudent

calls the has() trap.

The expression:

JavaScript

property in target

checks whether the property exists in the original object or its prototype chain.

8. The deleteProperty() Trap

The deleteProperty() trap intercepts the delete operator.

Syntax

JavaScript

deleteProperty(target, property) {
    // Custom logic
}

Example

JavaScript

const user = {
    name: "Kiran",
    age: 25
};

const handler = {
    deleteProperty(target, property) {
        console.log("Deleting:", property);

        delete target[property];

        return true;
    }
};

const proxyUser = new Proxy(user, handler);

delete proxyUser.age;

console.log(proxyUser);

Output

JavaScript

{
    name: "Kiran"
}

Explanation

When this statement executes:

JavaScript

delete proxyUser.age;

The deleteProperty() trap is called.

The property is deleted from the target object.

Practical use

This trap can be used to:

  • Prevent deletion of important properties.

  • Record deleted data.

  • Apply custom rules before removing properties.

9. The ownKeys() Trap

The ownKeys() trap intercepts operations that retrieve an object's own property keys.

Examples include:

JavaScript

Object.keys()
Object.getOwnPropertyNames()
Reflect.ownKeys()

Example

JavaScript

const user = {
    name: "Meena",
    age: 28,
    city: "Bengaluru"
};

const handler = {
    ownKeys(target) {
        return ["name", "city"];
    }
};

const proxyUser = new Proxy(user, handler);

console.log(Object.keys(proxyUser));

Output

["name", "city"]

Explanation

The target contains three properties:

JavaScript

name
age
city

However, the ownKeys() trap returns only:

JavaScript

["name", "city"]

Therefore, those are the keys returned by Object.keys(proxyUser).

Important restriction

The ownKeys() trap must follow JavaScript Proxy invariants. For example, it cannot arbitrarily hide certain non-configurable properties or omit required keys from a non-extensible target.

10. The apply() Trap

The apply() trap is used when the target is a function.

It intercepts function calls.

Syntax

JavaScript

apply(target, thisArg, argumentsList) {
    // Custom logic
}

Parameters

  • target: The original function.

  • thisArg: The value used as this.

  • argumentsList: The arguments passed to the function.

Example

JavaScript

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

const handler = {
    apply(target, thisArg, argumentsList) {

        console.log("Function called");

        return target.apply(thisArg, argumentsList);
    }
};

const proxyAdd = new Proxy(add, handler);

console.log(proxyAdd(10, 20));

Output

Function called
30

Explanation

When proxyAdd(10, 20) runs:

  1. The Proxy intercepts the function call.

  2. The apply() trap executes.

  3. The original function is called using target.apply().

  4. The result is returned.

Practical uses

The apply() trap is useful for:

  • Function logging.

  • Measuring execution time.

  • Adding access checks.

  • Creating function wrappers.

  • Tracking function calls.

11. The construct() Trap

The construct() trap intercepts the new operator.

It is used when the target is a constructor function.

Example

JavaScript

function Person(name) {
    this.name = name;
}

const handler = {
    construct(target, argumentsList, newTarget) {

        console.log("Creating a new Person");

        return Reflect.construct(
            target,
            argumentsList,
            newTarget
        );
    }
};

const ProxyPerson = new Proxy(Person, handler);

const person = new ProxyPerson("Arun");

console.log(person.name);

Output

Creating a new Person
Arun

Explanation

When:

JavaScript

new ProxyPerson("Arun");

is executed, the construct() trap is called.

The original constructor is invoked using:

JavaScript

Reflect.construct(target, argumentsList, newTarget);

The result is a newly created object.

12. What is the Reflect API?

The Reflect API is a built-in JavaScript object that provides methods for performing fundamental object operations.

It was introduced in ECMAScript 2015 (ES6).

Reflect methods are often used inside Proxy traps because they provide a standard way to forward operations to the target.

Common Reflect methods

|
Reflect method

|

Purpose

|
| --- | --- |
|

Reflect.get()

|

Read a property

|
|

Reflect.set()

|

Set a property

|
|

Reflect.has()

|

Check whether a property exists

|
|

Reflect.deleteProperty()

|

Delete a property

|
|

Reflect.ownKeys()

|

Get property keys

|
|

Reflect.getOwnPropertyDescriptor()

|

Get a property descriptor

|
|

Reflect.defineProperty()

|

Define a property

|
|

Reflect.getPrototypeOf()

|

Get the prototype

|
|

Reflect.setPrototypeOf()

|

Set the prototype

|
|

Reflect.isExtensible()

|

Check extensibility

|
|

Reflect.preventExtensions()

|

Prevent extensions

|
|

Reflect.apply()

|

Call a function

|
|

Reflect.construct()

|

Construct an object

|

Reflect does not create a Proxy. It provides methods for performing operations that a Proxy can intercept.

13. Reflect.get()

Reflect.get() retrieves a property value from an object.

Syntax

JavaScript

Reflect.get(target, property, receiver);

The receiver parameter is optional.

Example

JavaScript

const student = {
    name: "Suresh",
    age: 23
};

console.log(Reflect.get(student, "name"));
console.log(Reflect.get(student, "age"));

Output

Suresh
23

Equivalent operation

JavaScript

student.name

is similar to:

JavaScript

Reflect.get(student, "name");

Using Reflect.get() inside a Proxy

JavaScript

const student = {
    name: "Suresh",
    age: 23
};

const handler = {
    get(target, property, receiver) {

        console.log("Accessing:", property);

        return Reflect.get(target, property, receiver);
    }
};

const proxyStudent = new Proxy(student, handler);

console.log(proxyStudent.name);

Output

Accessing: name
Suresh

Why use Reflect.get()?

Instead of manually writing:

JavaScript

return target[property];

you can write:

JavaScript

return Reflect.get(target, property, receiver);

This is especially useful when working with inheritance, getters, and the receiver object.

14. Reflect.set()

Reflect.set() assigns a value to an object's property.

Syntax

JavaScript

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

Example

JavaScript

const employee = {
    name: "Asha",
    salary: 25000
};

const result = Reflect.set(employee, "salary", 35000);

console.log(result);
console.log(employee.salary);

Output

true
35000

Explanation

The method:

JavaScript

Reflect.set(employee, "salary", 35000);

assigns 35000 to the salary property.

It returns true when the assignment succeeds.

Using Reflect.set() in a Proxy

JavaScript

const employee = {
    name: "Asha",
    salary: 25000
};

const handler = {
    set(target, property, value, receiver) {

        console.log("Setting:", property);

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

const proxyEmployee = new Proxy(employee, handler);

proxyEmployee.salary = 35000;

console.log(proxyEmployee.salary);

Output

Setting: salary
35000

Reflect.set() is often preferred in a Proxy because it forwards the assignment while preserving the correct receiver behavior.

15. Reflect.has()

Reflect.has() checks whether a property exists in an object.

Syntax

JavaScript

Reflect.has(target, property);

Example

JavaScript

const product = {
    name: "Mobile",
    price: 15000
};

console.log(Reflect.has(product, "name"));
console.log(Reflect.has(product, "brand"));

Output

true
false

Equivalent operation

JavaScript

"name" in product

is similar to:

JavaScript

Reflect.has(product, "name");

Using it in a Proxy

JavaScript

const product = {
    name: "Mobile",
    price: 15000
};

const handler = {
    has(target, property) {

        console.log("Checking property:", property);

        return Reflect.has(target, property);
    }
};

const proxyProduct = new Proxy(product, handler);

console.log("price" in proxyProduct);

Output

Checking property: price
true

16. Reflect.deleteProperty()

This method deletes a property from an object.

Syntax

JavaScript

Reflect.deleteProperty(target, property);

Example

JavaScript

const student = {
    name: "Rohan",
    age: 20
};

const result = Reflect.deleteProperty(student, "age");

console.log(result);
console.log(student);

Output

true
{
    name: "