7 min read

Proxy Is for Proxies. What Is `Reflect` For?

Table of Contents

Cover Image

JavaScript’s Proxy feels immediately useful: it lets you intercept property reads, writes, function calls, construction, and more.

Reflect, at first glance, feels less dramatic. It does not create a new programming model. It does not wrap objects. It does not intercept anything. Most of its methods look like alternate spellings of things JavaScript already had.

So what is Reflect actually for?

The short version: Reflect exposes JavaScript’s internal object operations as ordinary functions, with predictable return values and excellent compatibility with Proxy handlers.

That sounds abstract, so let’s make it practical.

Reflect Is Mostly a Mirror

The name is accurate. Reflect reflects existing JavaScript operations into method form.

For example:

Reflect.get(target, propertyKey);
Reflect.set(target, propertyKey, value);

These correspond closely to:

target[propertyKey];
target[propertyKey] = value;

In everyday code, that means this:

input.value = 'zhangxinxu';

Can also be written as:

Reflect.set(input, 'value', 'zhangxinxu');

The visible effect is the same.

Here is a small demo-style version using an input field and three buttons: one for direct assignment, one for Reflect.set(), and one for Reflect.get().

const input = document.querySelector('#basicInput');
const output = document.querySelector('#basicOutput');

document.querySelector('#basicDirect').addEventListener('click', () => {
  input.value = 'direct assignment';
  output.textContent = "input.value = 'direct assignment'";
});

document.querySelector('#basicReflect').addEventListener('click', () => {
  Reflect.set(input, 'value', 'Reflect.set value');
  output.textContent = "Reflect.set(input, 'value', 'Reflect.set value')";
});

document.querySelector('#basicRead').addEventListener('click', () => {
  output.textContent =
    `Reflect.get(input, 'value') -> ${Reflect.get(input, 'value')}`;
});

This example shows the core idea: Reflect does not make property access more powerful by itself. It makes the operation explicit and function-shaped.

Demo animation

🎮 Try it live: Open the interactive demo to experience this yourself.

The First Real Benefit: Return Values

Direct assignment can be misleading.

When you write this:

input.type = 'number';

The expression evaluates to 'number', but that does not necessarily mean the assignment actually changed the property.

Reflect.set(), on the other hand, returns a boolean: true if the assignment succeeded, false if it did not.

That difference matters when a property is read-only, blocked by a descriptor, frozen, or otherwise not writable.

const input = document.querySelector('#readonlyTypeInput');
const output = document.querySelector('#readonlyTypeOutput');

Object.defineProperty(input, 'type', {
  configurable: true,
  get() {
    return this.getAttribute('type') || 'text';
  }
});

document.querySelector('#readonlyTypeRun').addEventListener('click', () => {
  const assignmentResult = (input.type = 'number');
  const reflectResult = Reflect.set(input, 'type', 'number');

  output.textContent = [
    `input.type = 'number' -> ${assignmentResult}`,
    `Reflect.set(input, 'type', 'number') -> ${reflectResult}`,
    `Actual input.type -> ${input.type}`
  ].join('\n');
});

The direct assignment expression reports the value being assigned. Reflect.set() reports whether the operation worked.

Demo animation

🎮 Try it live: Open the interactive demo to experience this yourself.

This is one of the best practical reasons to use Reflect: it gives you a clean result instead of forcing you to infer success afterward.

Reflect Also Helps Avoid Broken Control Flow

In strict mode, assigning to a frozen object can throw and stop execution.

With direct assignment:

(function () {
  'use strict';

  var frozen = { 1: 81 };
  Object.freeze(frozen);

  frozen[1] = 'zhangxinxu';

  console.log('no log');
})();

The console.log() line will not run, because the assignment throws a TypeError.

With Reflect.set():

(function () {
  'use strict';

  var frozen = { 1: 81 };
  Object.freeze(frozen);

  var ok = Reflect.set(frozen, '1', 'zhangxinxu');

  console.log(ok);      // false
  console.log('no log');
})();

The operation fails, but the code keeps moving. You get false instead of an exception interrupting the flow.

Why Proxy and Reflect Fit Together

Reflect becomes much more interesting when used with Proxy.

A Proxy handler often intercepts an operation, adds a little behavior, and then wants to perform the original operation normally. Reflect is perfect for that.

For example, this proxy dispatches a change event whenever someone sets value:

const input = document.querySelector('#proxyInput');
const output = document.querySelector('#proxyOutput');

const xyInput = new Proxy(input, {
  set(target, prop, value) {
    if (prop == 'value') {
      target.dispatchEvent(new CustomEvent('change'));
    }

    return Reflect.set(target, prop, value);
  },

  get(target, prop) {
    return Reflect.get(target, prop);
  }
});

input.addEventListener('change', () => {
  output.append('It changed~ ');
});

xyInput.value = 'zhangxinxu';

The handler does one custom thing, then delegates the actual assignment back to JavaScript’s normal behavior through Reflect.set().

That is the common Proxy pattern:

  1. Intercept the operation.
  2. Add custom behavior.
  3. Delegate to Reflect.

Demo animation

🎮 Try it live: Open the interactive demo to experience this yourself.

The receiver Parameter Is the Part You Cannot Replace Easily

Most of the time, Reflect.get(target, prop) feels equivalent to target[prop].

But Reflect.get() has a third parameter:

Reflect.get(target, prop, receiver);

That receiver controls what this should mean when a getter runs.

This becomes important with inheritance.

let miaoMiao = {
  _name: 'vaccine',
  get name() {
    return this._name;
  }
};

let noReceiverProxy = new Proxy(miaoMiao, {
  get(target, prop) {
    return target[prop];
  }
});

let reflectProxy = new Proxy(miaoMiao, {
  get(target, prop, receiver) {
    return Reflect.get(target, prop, receiver);
  }
});

let noReceiverChild = {
  __proto__: noReceiverProxy,
  _name: 'Sinovac vaccine'
};

let reflectChild = {
  __proto__: reflectProxy,
  _name: 'Sinovac vaccine'
};

console.log(noReceiverChild.name); // vaccine
console.log(reflectChild.name);    // Sinovac vaccine

Without Reflect.get(target, prop, receiver), the getter runs with the wrong this. It reads _name from the proxy target.

With receiver, the getter runs as if it belongs to the actual calling object, so it reads _name from the child object.

This is the strongest argument for Reflect in proxy code. Direct property access cannot express this behavior as clearly.

Common Reflect Methods and Their Older Counterparts

Most Reflect methods correspond to existing JavaScript syntax or object APIs:

Reflect methodSimilar to
Reflect.apply(target, thisArgument, argumentsList)Function.prototype.apply()
Reflect.construct(target, argumentsList)new target(...args)
Reflect.defineProperty(target, prop, attributes)Object.defineProperty()
Reflect.deleteProperty(target, prop)delete target[prop]
Reflect.get(target, prop, receiver)target[prop]
Reflect.getOwnPropertyDescriptor(target, prop)Object.getOwnPropertyDescriptor()
Reflect.getPrototypeOf(target)Object.getPrototypeOf()
Reflect.has(target, prop)prop in target
Reflect.isExtensible(target)Object.isExtensible()
Reflect.ownKeys(target)Object.keys() and symbol-aware key inspection
Reflect.preventExtensions(target)Object.preventExtensions()
Reflect.set(target, prop, value, receiver)target[prop] = value
Reflect.setPrototypeOf(target, prototype)Object.setPrototypeOf()

The important point is not that Reflect gives you totally new abilities everywhere. It gives you a consistent API surface for object operations.

So, What Is Reflect For?

Reflect is not flashy. It is not a replacement for Proxy, and it is not something you need in every object access.

Its value is more specific:

  • It turns object operations into function calls.
  • It gives useful boolean return values for operations like set(), defineProperty(), and deleteProperty().
  • It avoids some exception-driven control flow.
  • It pairs naturally with Proxy handlers.
  • It supports receiver, which matters for inherited getters and setters.

So yes, Proxy is for proxies.

Reflect is for performing the original JavaScript operation clearly, predictably, and with the right context.


Try It Yourself

Want to see these concepts in action? I’ve created an interactive demo where you can experiment with the code and see real-time results.

View the Live Demo

Explore more demos from my previous articles in the Demo Gallery.

Happy coding!