skip to content

JavaScript's Reflect namespace duplicates work that Object.* methods and operators like `in`, `delete` and `new` already do. What is Reflect for, and how does it actually differ from those older APIs?

level: middleimportance: must knowfreq 58%

answer

  1. one function per internal operation
  2. same names as the trap list
  3. booleans where the twins throw
  4. primitives rejected, not coerced
  5. operators become passable values

basics

~20 s

Reflect is a plain namespace object holding one function per internal object operation, matching the Proxy trap list one for one. Unlike the Object.* helpers and operators it mirrors, its methods report failure by returning false and never coerce primitive arguments.

solid answer

~40 s

`Reflect` is a single built-in object — not a constructor, not callable — that exposes each of the language's internal object operations as an ordinary function: `Reflect.get`, `set`, `has`, `deleteProperty`, `ownKeys`, `defineProperty`, `getPrototypeOf`, `setPrototypeOf`, `apply`, `construct`, and a few more. Its method list matches the `Proxy` handler trap list name for name and argument for argument, which is why a well-written trap forwards to the same-named `Reflect` call to get default behaviour. The differences from the older APIs are real, not cosmetic: `Reflect.defineProperty` returns `false` where `Object.defineProperty` throws; `Reflect.getPrototypeOf('abc')` throws a `TypeError` where `Object.getPrototypeOf('abc')` quietly coerces the string to a wrapper; and `Reflect.has`/`Reflect.construct` are functions, so they compose where `in` and `new` — being operators — cannot. `Reflect.get` and `Reflect.set` also take a `receiver` argument that no `Object.*` method has.

code

javascript · 17 lines
javascript
const obj = { a: 1 };
Object.freeze(obj);

// Failure reported as a value, not an exception
console.log(Reflect.defineProperty(obj, 'b', { value: 2 })); // false

// Primitives rejected instead of coerced
console.log(Object.getPrototypeOf('abc') === String.prototype); // true
try {
  Reflect.getPrototypeOf('abc');
} catch (e) {
  console.log(e.constructor.name); // "TypeError"
}

// Operators become ordinary, passable functions
const keys = ['a', 'b'];
console.log(keys.filter(k => Reflect.has(obj, k))); // ['a']

go deeper

for a junior

Be able to say that Reflect is a built-in namespace object of static functions — like Math or JSON — and that you never write new Reflect(). Naming two or three of its methods is enough at this level.

for a middle

Explain the one-method-per-internal-operation design and the exact mirror of the Proxy trap list, then give the concrete behavioural differences: boolean returns instead of throws, and a TypeError instead of primitive coercion.

for a senior

Show why the design choice matters in code you ship: forwarding traps get spec-correct defaults for free, and boolean returns let you branch on a failed define or set without wrapping everything in try/catch. Say plainly when you would not reach for Reflect.

for a principal

Own the API-surface argument: Reflect deliberately stops at the object protocol while Object keeps the ergonomic composites, and that split is what keeps the meta layer stable. Be ready to judge whether a team's reflective abstraction is worth its debuggability cost at all.

## What Reflect actually is `Reflect` is one ordinary built-in object, introduced in ES2015. It is **not** a constructor and **not** a function: both `new Reflect()` and `Reflect()` throw a `TypeError`. It has no prototype-based API to inherit from — it is simply a namespace, in the same spirit as `Math` or `JSON`. It carries a `Symbol.toStringTag` of `"Reflect"`, so `Object.prototype.toString.call(Reflect)` returns `"[object Reflect]"`. ```js typeof Reflect; // "object" Object.prototype.toString.call(Reflect); // "[object Reflect]" ``` ## One function per internal operation Every JavaScript object answers a fixed set of *internal methods* the specification defines: `[[Get]]`, `[[Set]]`, `[[HasProperty]]`, `[[Delete]]`, `[[OwnPropertyKeys]]`, `[[DefineOwnProperty]]`, `[[GetOwnProperty]]`, `[[GetPrototypeOf]]`, `[[SetPrototypeOf]]`, `[[IsExtensible]]`, `[[PreventExtensions]]`, and — for callable objects — `[[Call]]` and `[[Construct]]`. Before ES2015 these were reachable only through a scattered mix of operators (`obj.x`, `x in obj`, `delete obj.x`, `new C()`), `Object.*` helpers, and `Function.prototype` methods. `Reflect` gives each one a plain function with a uniform shape: ```js Reflect.get(obj, 'x'); // obj.x Reflect.set(obj, 'x', 1); // obj.x = 1 Reflect.has(obj, 'x'); // 'x' in obj Reflect.deleteProperty(obj, 'x'); // delete obj.x Reflect.apply(fn, thisArg, args); // fn.apply(thisArg, args) Reflect.construct(C, args); // new C(...args) ``` That is thirteen methods in total. (An extra `Reflect.enumerate` appeared in the ES2015 draft and was removed in ES2016 — do not expect it to exist.) ## Why the shape matters: the Proxy connection A `Proxy` handler's traps are named after the very same internal operations, and each trap receives exactly the arguments the matching `Reflect` method takes. That one-to-one correspondence is deliberate: it makes "do the default thing" a single mechanical call rather than a hand-written reimplementation. ```js const logged = new Proxy(target, { get(t, key, receiver) { console.log('read', key); return Reflect.get(t, key, receiver); // exact default behaviour } }); ``` Without `Reflect`, the trap author would have to re-derive default semantics by hand — walking descriptors, invoking getters with the right `this`, checking the prototype chain — and would get it subtly wrong. ## Three real differences from the older APIs **1. Failure is a return value, not an exception.** `Reflect.defineProperty`, `set`, `deleteProperty`, `preventExtensions` and `setPrototypeOf` all return a boolean. Their `Object.*` twins either throw a `TypeError` on failure or return the object, so you cannot branch on the outcome without a `try`/`catch`. This is not a stylistic preference: traps are *required* to return booleans, so `Reflect` had to speak that language. **2. No primitive coercion.** Since ES2015 many `Object.*` methods quietly coerce a primitive to its wrapper instead of complaining. `Reflect` refuses: ```js Object.getPrototypeOf('abc'); // String.prototype Reflect.getPrototypeOf('abc'); // TypeError: Reflect.getPrototypeOf called on non-object Object.keys('abc'); // ['0', '1', '2'] Reflect.ownKeys('abc'); // TypeError ``` For meta-programming that strictness is a feature — a primitive reaching a reflective call is almost always a bug you want surfaced. **3. Functions instead of operators.** `in`, `delete` and `new` are syntax; you cannot pass them to `map`, store them in a table, or apply them to a dynamic argument list. `Reflect.has`, `Reflect.deleteProperty` and `Reflect.construct` are values. ```js const present = ['a', 'b', 'c'].filter(k => Reflect.has(obj, k)); ``` ## What Reflect is not It is not a replacement for `Object`. There is no `Reflect.keys`, `Reflect.entries`, `Reflect.assign` or `Reflect.freeze` — the everyday, ergonomic, enumerable-string-key helpers stay on `Object`. `Reflect.ownKeys` is the closest thing, and it deliberately returns *all* own keys, strings and symbols, enumerable or not. The division is: `Object.*` is the application-level convenience layer, `Reflect.*` is the meta-programming layer that mirrors the object protocol exactly. Outside of proxies, the honest answer for most application code is that you will reach for `Object.*` and rarely touch `Reflect` at all.

  • If Reflect mostly mirrors things that already existed, what would actually break in a Proxy handler written without it?
    Nothing crashes, but defaults become hand-rolled and drift from the spec. A `get` trap returning `target[key]` invokes getters with the wrong `this`; a `set` trap using `target[key] = value` silently ignores the receiver and returns the wrong boolean; an `ownKeys` trap built from `Object.keys` drops symbols and non-enumerable keys, which then trips the proxy's own invariant checks.
  • Why is there no Reflect.keys or Reflect.assign?
    Because neither corresponds to an internal object operation. `Object.keys` is a filtered, string-only, enumerable-only view built on top of `[[OwnPropertyKeys]]`, and `Object.assign` is a copying loop over several operations. `Reflect` only exposes the primitive protocol, one method per internal method; anything composed or filtered stays on `Object`.
  • Is Reflect useful outside of Proxy code?
    Occasionally. `Reflect.has` and `Reflect.deleteProperty` compose where the operators cannot; `Reflect.ownKeys` is the one call that returns strings and symbols together; `Reflect.defineProperty` lets you branch on failure without a try/catch; and `Reflect.construct` can pass a `newTarget`. But for ordinary application code `Object.*` remains the idiomatic choice.

saying these in an interview costs you the question

  • Reflect is a class you instantiate with new Reflect()
  • Reflect deprecates Object and its static methods
  • Reflect methods throw on failure exactly like the Object twins
  • Reflect only works inside a Proxy handler
  • Reflect.ownKeys is just another name for Object.keys

context