Skip to main content

Why is Reflect needed together with Proxy?

The problem before Reflect existed

Before ES6, there was no way at all to intercept operations on an object. With the introduction of Proxy this became possible, but it immediately raised another problem:

How do you invoke the "default behavior" after intercepting an operation?

For example, you want to log property access, but not break the object's behavior:

javascript
const user = { name: "Tim" }; const proxy = new Proxy(user, { get(target, prop, receiver) { console.log(`Reading property "${prop}"`); return target[prop]; // works, but not always correctly } });

This is fine so far, but if:

  • the property is on the prototype,
  • or it's a getter,
  • or inheritance is involved,

then plain target[prop] can behave differently from regular obj.prop.


The solution: Reflect

To keep Proxy traps from breaking JS's normal behavior, Reflect was introduced.

Reflect provides functional counterparts to all the language's basic operations that Proxy can intercept.

For example:

javascript
const proxy = new Proxy(user, { get(target, prop, receiver) { console.log(`Reading "${prop}"`); // a safe call to the default behavior: return Reflect.get(target, prop, receiver); }, set(target, prop, value, receiver) { console.log(`Writing "${prop}" = ${value}`); return Reflect.set(target, prop, value, receiver); } });

Now the proxy behaves exactly like the original object, just with extra logic added.


Why you can't just use target[prop]

Here's an example where the behavior breaks without Reflect

javascript
const proto = { get name() { console.log("Getter from the prototype"); return "Alex"; } }; const obj = Object.create(proto); const proxy = new Proxy(obj, { get(target, prop) { console.log("Intercepting get"); return target[prop]; // the getter runs with the wrong this } }); console.log(proxy.name);

Result:

javascript
Intercepting get Getter from the prototype

but inside the getter, this will refer to target, not proxy.


The fix, using Reflect.get:

javascript
const proxy = new Proxy(obj, { get(target, prop, receiver) { console.log("Intercepting get"); return Reflect.get(target, prop, receiver); // correct } }); console.log(proxy.name); // Intercepting get // Getter from the prototype

Now this inside the getter correctly points to proxy.


A one-to-one relationship

Every Proxy trap has a matching Reflect method that performs the default behavior:

Proxy trapReflect methodWhat it does
getReflect.get()Reads a property
setReflect.set()Sets a value
hasReflect.has()Checks prop in obj
deletePropertyReflect.deleteProperty()Deletes a property
ownKeysReflect.ownKeys()Gets all keys
getOwnPropertyDescriptorReflect.getOwnPropertyDescriptor()Gets a descriptor
definePropertyReflect.defineProperty()Defines a property
getPrototypeOfReflect.getPrototypeOf()Gets the prototype
setPrototypeOfReflect.setPrototypeOf()Changes the prototype
isExtensibleReflect.isExtensible()Checks extensibility
preventExtensionsReflect.preventExtensions()Prevents extension
applyReflect.apply()Calls a function
constructReflect.construct()Calls via new

So in every Proxy trap you can simply add your own logic and delegate the default operation to Reflect.


Example: logging every operation without breaking the logic

javascript
const user = { name: "Tim" }; const proxy = new Proxy(user, { get(target, prop, receiver) { console.log(`GET ${prop}`); return Reflect.get(target, prop, receiver); }, set(target, prop, value, receiver) { console.log(`SET ${prop} = ${value}`); return Reflect.set(target, prop, value, receiver); }, deleteProperty(target, prop) { console.log(`DELETE ${prop}`); return Reflect.deleteProperty(target, prop); } }); proxy.name; // GET name proxy.age = 30; // SET age = 30 delete proxy.age;// DELETE age

Without Reflect you'd have to manually emulate the language's behavior (which is difficult and unsafe).


Additional advantages of Reflect

  1. Its methods return true/false instead of throwing errors. Convenient for checking whether an operation succeeded.
javascript
Reflect.defineProperty({}, 'x', { value: 1 }); // true
  1. You can programmatically perform operations that are normally expressed with operators (delete, in, new, calling a function).
javascript
Reflect.apply(Math.max, null, [1, 2, 3]); // 3 Reflect.construct(Date, [2025, 0, 1]); // equivalent to new Date(2025, 0, 1)
  1. You build a Proxy, safely delegate through Reflect, and never break the language.

Summary

WhatRole
ProxyIntercepts operations on an object
ReflectLets you perform the "original" operation inside a trap
Why togetherTo extend or modify an object's behavior without breaking standard JS semantics
AdvantagePredictable code that's compatible with inheritance, getters/setters, and built-in objects

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.