Reflect
Learn the Reflect API: a function-based service desk for object operations, proxy forwarding, own keys, receivers, apply, and construct.
- 01Name the service-desk methodsMatch common object operations to the Reflect method that performs them.
- 02Forward proxy traps safelyUse matching Reflect calls and return the booleans strict mode expects.
- 03Preserve receiversExplain why
Reflect.get(target, key, receiver)matters for getters.
Reflect is the object model’s service desk
JavaScript already has many ways to perform object operations: dot access, bracket access, in, delete, new, Object.defineProperty, and Function.prototype.apply. Reflect gathers the core operations behind those tools into plain functions.
That sounds abstract until you write a Proxy. A proxy trap intercepts an operation, and very often you want to log, validate, or tweak one thing, then do the normal operation. Reflect gives you the matching official forwarding function.
Imagine a building where many small tasks normally happen by gestures: opening a door, checking a name plate, removing a sign. Reflect is the desk where you can request those same tasks by filling out a clear form. The answer is often a simple yes or no.
- In real life: Ask the desk to check whether a room exists
- In JavaScript:
Reflect.has(target, key) - In real life: Ask the desk to unlock or delete a room
- In JavaScript:
Reflect.deleteProperty(target, key) - In real life: Ask the desk to create a room and report success
- In JavaScript:
Reflect.defineProperty(target, key, descriptor) - In real life: The desk says yes or no instead of shouting
- In JavaScript: Many mutating Reflect methods return booleans
Where the analogy stops: The service desk does not make impossible operations possible. It still follows descriptors, extensibility, proxy invariants, and TypeError rules for non-object targets.
Reflect is a namespace of 13 methods that match the 13 proxy traps. Use it when you want the function form of an operation, especially inside proxy traps.
In this lesson you will compare Reflect with Object methods, preserve a getter’s receiver, forward proxy traps, inspect key order, and use Reflect.apply and Reflect.construct for advanced calls.
The 13 Reflect methods map to operations
SORTERReflect is not magic. Each method is a named function for an operation JavaScript already knows how to do. The full list is: apply, construct, defineProperty, deleteProperty, get, getOwnPropertyDescriptor, getPrototypeOf, has, isExtensible, ownKeys, preventExtensions, set, and setPrototypeOf.
| Operation | Reflect method | Important return |
|---|---|---|
| Read a property | Reflect.get(target, key, receiver?) | The property value; accepts a receiver for getters. |
| Write a property | Reflect.set(target, key, value, receiver?) | true when the write succeeds, false when it is blocked. |
| Check a key | Reflect.has(target, key) | Boolean, like the in operator. |
| Delete a key | Reflect.deleteProperty(target, key) | Boolean, like delete target[key] but callable. |
| List every own key | Reflect.ownKeys(target) | String keys and symbol keys, including non-enumerable ones. |
| Call or construct | Reflect.apply / Reflect.construct | The return value of the call or the newly constructed object. |
The methods that change shape or write state often return a boolean: defineProperty, set, deleteProperty, preventExtensions, and setPrototypeOf. That boolean is especially useful in strict-mode code and proxy traps.
| Reflect method | When it returns false |
|---|---|
Reflect.defineProperty | The descriptor cannot be installed, for example on a non-extensible object. |
Reflect.set | The write is blocked by a setter, descriptor, or proxy invariant. |
Reflect.deleteProperty | A non-configurable property cannot be deleted. |
Reflect.preventExtensions | The object cannot be made non-extensible. |
Reflect.setPrototypeOf | The prototype change is not allowed. |
- Get
user.namewhile preserving a getter receiver - Assign
user.name = valuefrom a trap - Check whether
name in useris true - List strings, symbols, and hidden own keys
- Install a descriptor and get
trueorfalse - Try to change an object's prototype
- Call a function with a chosen
thisand an args array - Call a constructor with a custom
new.target
Drag each card to the kind of operation its Reflect method performs.
Reflect vs Object and syntax
STEP THROUGHSome Object methods and operators throw or coerce in ways that are awkward when you are writing generic code. Object.defineProperty throws if it cannot define; Reflect.defineProperty returns false. Object.getPrototypeOf(1) coerces a primitive wrapper in modern JavaScript, while Reflect.getPrototypeOf(1) throws TypeError because Reflect expects an object target.
Step through the same failing definition with Object and Reflect. Predict which one throws and which one returns a boolean.
script
"use strict";let objectResult;try { Object.defineProperty(locked, "id", { value: 1 });} catch (error) { objectResult = error.name;}const reflectResult = Reflect.defineProperty(locked, "id", { value: 1 });console.log(objectResult);console.log(reflectResult);Here is the broader quick map: Reflect.has mirrors in, Reflect.deleteProperty mirrors delete, and Reflect.ownKeys sees more keys than Object.keys.
const box = { visible: 1 };const sym = Symbol("tag");Object.defineProperty(box, "hidden", { value: 2 });box[sym] = 3; console.log(Reflect.has(box, "visible"));console.log(Reflect.deleteProperty(box, "missing"));console.log(Reflect.ownKeys(box));console.log(Object.keys(box));Run this in a console to see booleans for has/delete, then compare the all-keys list with the enumerable string-only list.
Reflect.has matches in, Reflect.deleteProperty matches delete, and Reflect.ownKeys sees more than Object.keys. Reflect methods still throw TypeError when the target must be an object and you pass a primitive.
const box = { visible: 1 };
const sym = Symbol("tag");
Object.defineProperty(box, "hidden", { value: 2 });
box[sym] = 3;
console.log(Reflect.has(box, "visible"));
console.log(Reflect.deleteProperty(box, "missing"));
console.log(Reflect.ownKeys(box));
console.log(Object.keys(box));Receivers: who is asking?
INTERACTIVEThe receiver is the object that should become this when a getter or setter runs. With plain obj.value, the receiver is usually obj. In inheritance and proxies, the target that owns the getter and the object that asked for the value can be different.
If a getter says “look at my name badge,” JavaScript must know whose badge it means. The receiver is that person at the counter. Reflect lets you pass the badge along instead of accidentally swapping it.
- In real life: A receptionist answers a question for the person at the counter
- In JavaScript: A getter runs with
thisset to the receiver - In real life: Forwarding the request but changing the person’s name badge
- In JavaScript: Using
target[key]can run the getter with the wrongthis - In real life: Forwarding the exact request slip
- In JavaScript:
Reflect.get(target, key, receiver)preserves the asker
Where the analogy stops: The receiver matters only for operations that use this, such as getters, setters, and some proxy chains. A plain data property just returns its stored value.
Switch line 12. The printed value is the same here, but the explanation shows why receiver matters for getters and proxies.
script
"use strict"; name: "prototype badge", get label() { return this.name; }};const user = Object.create(badge);user.name = "Ada";const proxy = new Proxy(user, { get(target, key, receiver) { return target[key]; }});console.log(proxy.label);In a simple example both versions may print the same value. The bug appears when the getter does more work, when a setter validates using this, or when another proxy should see the nested name read. Inside a proxy get trap, prefer return Reflect.get(target, key, receiver).
Forwarding traps with the matching Reflect method
STEP THROUGHA proxy handler often looks like a switchboard: log the operation, then forward it. The safest default is to accept the trap’s arguments with ...args and pass those exact arguments to the matching Reflect method.
Follow a logging proxy that forwards with Reflect. Watch for the boolean returned by Reflect.set.
script
"use strict";const log = [];const proxy = new Proxy(target, { get(...args) { log.push("get:" + String(args[1])); return Reflect.get(...args); }, set(...args) { log.push("set:" + String(args[1])); return Reflect.set(...args); }});proxy.count = proxy.count + 1;console.log(target.count);console.log(log.join(", "));A set trap must return a boolean. In strict mode, if it returns false or accidentally returns undefined, the assignment throws a TypeError. return Reflect.set(...args) both performs the normal write and returns the success flag.
Reflect.ownKeys sees the whole key shelf
INTERACTIVEObject.keys is intentionally friendly: own, enumerable, string keys only. Reflect.ownKeys is the complete own-key list: integer-like string keys in ascending order, then other string keys in insertion order, then symbols in insertion order. It includes non-enumerable keys.
const sym = Symbol("id");const record = { 20: "twenty", name: "Ada", 3: "three" };Object.defineProperty(record, "hidden", { value: true });record[sym] = "symbol value"; console.log(Reflect.ownKeys(record));console.log(Object.keys(record));- Reflect.ownKeys
3, 20, name, hidden, Symbol(id) - Object.keys
3, 20, name
The underlying run always uses Reflect.ownKeys. The toggles only help you compare which parts Object.keys would leave out. Integer-like keys appear first in numeric order.
That ordering is why arrays and array-like objects feel consistent, while symbols still remain separate from ordinary string names. Review the Symbols lesson if symbol keys are still new.
Reflect.apply and Reflect.construct
INTERACTIVEReflect.apply(fn, thisArg, args) calls a function with a chosen this and an array-like list of arguments. It is the modern, symmetrical cousin of fn.apply(thisArg, args), which you met in the call, apply & bind lesson.
Reflect.construct(Target, args, NewTarget) is more unusual. It runs Target as a constructor, but the optional third argument controls new.target inside the constructor and the prototype used for the created object. That connects to the lessons on constructors and new.
function total(prefix, ...numbers) { return prefix + numbers.reduce((sum, n) => sum + n, 0);}function Base(name) { this.name = name; this.createdBy = new.target === Special ? "Special" : "Base";}function Special() {} console.log(Reflect.apply(total, null, ["$", 2, 3, 4]));const item = Reflect.construct(Base, ["Ada"], Special);console.log(item.createdBy);console.log(Object.getPrototypeOf(item) === Special.prototype);- Reflect.apply$9
- new.target labelSpecial
- Special prototype?true
Reflect.apply calls a function with an argument array. Reflect.construct calls Base, but the third argument makes new.target and the final prototype come from Special.
function total(prefix, ...numbers) {
return prefix + numbers.reduce((sum, n) => sum + n, 0);
}
function Base(name) {
this.name = name;
this.createdBy = new.target === Special ? "Special" : "Base";
}
function Special() {}
console.log(Reflect.apply(total, null, ["$", 2, 3, 4]));
const item = Reflect.construct(Base, ["Ada"], Special);
console.log(item.createdBy);
console.log(Object.getPrototypeOf(item) === Special.prototype);Where you’ll use Reflect
Reflect shows up most often in libraries: validation proxies, observable objects, RPC layers, test doubles, data mappers, and frameworks that need to forward JavaScript’s normal object behavior while adding a little work around it.
Check a value in set, then return Reflect.set(...args) if it is valid.
Log get, set, and deleteProperty without reimplementing descriptors.
Try a definition and branch on Reflect.defineProperty returning false.
Use Reflect.ownKeys when non-enumerable or symbol metadata matters.
The habit is simple: if you are inside a proxy trap and want ordinary behavior, reach for the matching Reflect method first. Then add only the extra behavior you actually need.
Common misconceptions
- “Reflect never throws.” Many methods return booleans for operation failure, but Reflect methods still throw
TypeErrorwhen the target must be an object and is not. - “Reflect is only for proxies.” Proxies are the main use, but the function forms are useful in generic helpers too.
- “Reflect.ownKeys is the same as Object.keys.” It includes non-enumerable strings and symbols;
Object.keysdoes not. - “The receiver is always the target.” In inherited getters and proxy traps, the receiver can be the original object that asked.
- “Reflect.construct just avoids
new.” Its third argument controlsnew.targetand the new object’s prototype. - “A set trap can forget to return.” In strict mode that becomes
undefined, which means failure and throws.
Practice exercises
5 EXERCISESRead the code and predict the two console lines.
"use strict";
const box = Object.preventExtensions({});
let objectResult;
try { Object.defineProperty(box, "x", { value: 1 }); } catch (error) { objectResult = error.name; }
console.log(objectResult);
console.log(Reflect.defineProperty(box, "x", { value: 1 }));"use strict";
const box = Object.preventExtensions({});
let objectResult;
try { Object.defineProperty(box, "x", { value: 1 }); } catch (error) { objectResult = error.name; }
console.log(objectResult);
console.log(Reflect.defineProperty(box, "x", { value: 1 }));Object.defineProperty throws TypeError. Reflect.defineProperty tries the same definition and returns false.
Explain why this prints the user’s name even though the getter is stored on the prototype.
"use strict";
const proto = { get label() { return this.name; } };
const user = Object.create(proto);
user.name = "Ada";
console.log(Reflect.get(proto, "label", user));"use strict";
const proto = { get label() { return this.name; } };
const user = Object.create(proto);
user.name = "Ada";
console.log(Reflect.get(proto, "label", user));Reflect.get(proto, "label", user) runs the getter with this set to user, so this.name is Ada.
Predict the joined key order.
const sym = Symbol("s");
const obj = { 2: "two", first: 1, 1: "one" };
Object.defineProperty(obj, "hidden", { value: true });
obj[sym] = 9;
console.log(Reflect.ownKeys(obj).map((key) => typeof key === "symbol" ? key.toString() : key).join("|"));const sym = Symbol("s");
const obj = { 2: "two", first: 1, 1: "one" };
Object.defineProperty(obj, "hidden", { value: true });
obj[sym] = 9;
console.log(Reflect.ownKeys(obj).map((key) => typeof key === "symbol" ? key.toString() : key).join("|"));Reflect.ownKeys reports 1, 2, then the inserted strings first and hidden, then Symbol(s).
What error name prints, and what one-word fix belongs before Reflect.set?
"use strict";
const proxy = new Proxy({}, { set(target, key, value, receiver) { Reflect.set(target, key, value, receiver); } });
try { proxy.answer = 42; } catch (error) { console.log(error.name); }"use strict";
const proxy = new Proxy({}, {
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
}
});
proxy.answer = 42;The trap performs the write but forgets to return Reflect.set(...). Strict assignment treats undefined as failure and throws TypeError.
Predict the line printed by Reflect.apply.
function add(prefix, a, b) { console.log(prefix + (a + b)); }
Reflect.apply(add, null, ["total:", 2, 5]);function add(prefix, a, b) { console.log(prefix + (a + b)); }
Reflect.apply(add, null, ["total:", 2, 5]);The arguments become prefix = "total:", a = 2, and b = 5, so the function logs total:7.
Check your understanding
7 QUESTIONSQuestion 1 of 7Which statement best describes Reflect?
Choose an answer to see the explanation.
Question 2 of 7What does the failed Reflect definition print?
Read the code, then predictconst locked = Object.preventExtensions({}); console.log(Reflect.defineProperty(locked, "x", { value: 1 }));Choose an answer to see the explanation.
Question 3 of 7Which Reflect method is the function form of the
inoperator?Choose an answer to see the explanation.
Question 4 of 7What does this ownKeys order print?
Read the code, then predictconst s = Symbol("s"); const obj = { 10: "ten", a: 1, 2: "two" }; Object.defineProperty(obj, "hidden", { value: true }); obj[s] = 3; console.log(Reflect.ownKeys(obj).map((k) => typeof k === "symbol" ? k.toString() : k).join(","));Choose an answer to see the explanation.
Question 5 of 7Why return
Reflect.set(...args)from a proxy set trap?Choose an answer to see the explanation.
Question 6 of 7What does Reflect.apply print here?
Read the code, then predictfunction join(a, b) { console.log(this.prefix + a + b); } Reflect.apply(join, { prefix: "#" }, [2, 3]);Choose an answer to see the explanation.
Question 7 of 7What does Reflect.construct's third argument control?
Choose an answer to see the explanation.
Key takeaways
- Reflect exposes JavaScript’s object operations as plain functions.
- There are 13 Reflect methods, matching the 13 proxy traps.
- Several mutating Reflect methods return booleans instead of throwing for ordinary operation failure.
- Use
Reflect.getandReflect.setwith the receiver when forwarding proxy traps. Reflect.ownKeysreturns integer-like strings, other strings, then symbols, including non-enumerable keys.Reflect.constructcan setnew.targetwith its third argument.
Reflect is the standard function-based API for performing and forwarding JavaScript object operations.
Up next: Well-known symbols.