Reflect
Learn the Reflect API: a function-based service desk for object operations, proxy forwarding, own keys, receivers, apply, and construct.
- 01Match jobs to methodsName the Reflect method for common object jobs, like reading, writing, and deleting a property.
- 02Pass proxy traps on safelyCall the matching Reflect method inside a trap, and return its
trueorfalseanswer. - 03Keep
thisrightExplain whyReflect.get(target, key, receiver)matters when a getter runs.
Reflect does object jobs with plain functions
You already know many ways to work with an object:
- read a property, like
cart.tea - check for a key with
in, like"tea" in cart - remove a key with
delete - make a new object with
new - add a property with
Object.defineProperty
Reflect is a built-in object that holds functions for these same jobs. Each job becomes a plain function call, like Reflect.has(cart, "tea").
Why use a function when the short syntax works? The main reason is a proxy. A proxy is an object that stands in front of another object, called the target. You met proxies in the Proxy lesson. A proxy has traps: functions that catch an action, like a read or a write. A trap often does one small extra thing, like writing a log. Then it wants to do the normal action. Reflect has one function for each normal action, so the trap can call it.
Picture a big building. Most small jobs happen by hand: you open a door, read a name plate, or take down a sign. The building also has a service desk. At the desk, you fill in a clear form for the same jobs, and the desk does them for you. The desk often answers with a simple yes or no.
- In real life: Ask the desk whether a room exists
- In JavaScript:
Reflect.has(target, key) - In real life: Ask the desk to remove a room
- In JavaScript:
Reflect.deleteProperty(target, key) - In real life: Ask the desk to set up a new room and say if it worked
- In JavaScript:
Reflect.defineProperty(target, key, descriptor) - In real life: The desk answers yes or no, calmly
- In JavaScript: Many Reflect methods that change an object return
trueorfalse
Reflect is a built-in object with 13 methods. Each method does one basic object job, like reading, writing, or deleting a property. The 13 methods match the 13 proxy traps, one for one. Use Reflect when you want an object job as a function call, most of all inside proxy traps.
Start with a normal object. Three Reflect calls check for a key, delete it, and check again.
const cart = { tea: 2 };
console.log(Reflect.has(cart, "tea"));
console.log(Reflect.deleteProperty(cart, "tea"));
console.log(Reflect.has(cart, "tea"));In plain words, line by line:
- Line 1 makes a small
cartobject with one key,tea. - Line 2 asks: does
carthave a key namedtea? It does, so it printstrue. - Line 3 deletes
tea. The delete works, so it printstrue. - Line 4 asks again. The key is gone, so it prints
false.
In this lesson you will:
- compare Reflect with Object methods
- keep a getter's
thisright when a proxy passes a read on - pass proxy traps on to Reflect
- list every key of an object, in order
- call functions and constructors with
Reflect.applyandReflect.construct
The 13 Reflect methods, one per object job
SORTERReflect is not magic. Each method is a named function for a job JavaScript already knows how to do. Here are all 13: apply, construct, defineProperty, deleteProperty, get, getOwnPropertyDescriptor, getPrototypeOf, has, isExtensible, ownKeys, preventExtensions, set, and setPrototypeOf.
Read each row of the next table from left to right: the job, the Reflect method that does it, and what the method gives back.
| The job | Reflect method | What it gives back |
|---|---|---|
| Read a property | Reflect.get(target, key, receiver?) | The value. The optional receiver becomes this if a getter runs. |
| Write a property | Reflect.set(target, key, value, receiver?) | true if the write worked, false if it was blocked. |
| Check for a key | Reflect.has(target, key) | true or false, like the in operator. |
| Delete a key | Reflect.deleteProperty(target, key) | true or false, like delete target[key]. |
| List every own key | Reflect.ownKeys(target) | An array of string and symbol keys, hidden ones too. |
| Call a function, or make an object with it | Reflect.apply / Reflect.construct | What the function returns, or the new object. |
Five methods change an object and answer true or false. The answer tells you whether the change worked:
definePropertysetdeletePropertypreventExtensionssetPrototypeOf
This answer is most useful inside proxy traps. A set trap, for example, must give back true or false.
A false answer does not mean Reflect never throws. Reflect still throws a TypeError in these cases:
- The target is not an object, for example the number
1. Reflect.applygets something that is not a function.- A proxy breaks one of its fixed rules. The Proxy lesson calls these rules invariants.
The next table names each of the five methods and the case where it returns false.
| Reflect method | When does it return false? |
|---|---|
Reflect.defineProperty | When the property can't be added, for example on an object that can't grow. |
Reflect.set | When the write is blocked. For example, the property is read-only, has a getter but no setter, or a proxy trap refuses. |
Reflect.deleteProperty | When the property is locked with configurable: false. |
Reflect.preventExtensions | When a proxy trap refuses. A plain object always answers true. |
Reflect.setPrototypeOf | When the change isn't allowed, for example on an object that can't grow. |
- Read
user.nameand keep the getter'sthisright - Do
user.name = valuefrom inside a trap - Check whether
name in useris true - List string keys, symbol keys, and hidden keys
- Add a property with flags and get
trueorfalse - Try to change an object's prototype
- Call a function with a chosen
thisand an array of arguments - Use a constructor with a different
new.target
Drag each card to the kind of job its Reflect method does.
Reflect compared with Object methods
STEP THROUGHSome Object methods act differently from their Reflect twins. Two differences matter most:
- When the job fails.
Object.definePropertythrows an error.Reflect.definePropertyreturnsfalse. - When the target is not an object.
Object.getPrototypeOf(1)treats the number like a number object and returnsNumber.prototype.Reflect.getPrototypeOf(1)throws aTypeError, because Reflect only accepts objects.
Step through the first difference. Line 2 uses Object.preventExtensions, which stops an object from growing: no new properties can be added to it. Then the code tries to add id two ways.
Step through the same failed job with Object and with Reflect. Guess first: which one throws, and which one returns false?
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);id to an object that can't grow. Object.defineProperty throws a TypeError; Reflect.defineProperty just returns false.Here are three more pairs:
Reflect.hasdoes the same job asin.Reflect.deletePropertydoes the same job asdelete.Reflect.ownKeyslists more keys thanObject.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));Lines 6 and 7 print true. Line 8 lists visible, hidden, and Symbol(tag). Line 9 lists only visible.
Reflect.has does the job of in. Reflect.deleteProperty does the job of delete. Reflect.ownKeys lists more keys than Object.keys. Reflect still throws a TypeError if you give it a number or a string instead of an object.
Next, see two kinds of failure side by side: a thrown error, and a false answer.
try {
Reflect.getPrototypeOf(1);
} catch (error) {
console.log(error.name);
}
const box = {};
Object.defineProperty(box, "id", { value: 1, configurable: false });
console.log(Reflect.deleteProperty(box, "id"));In plain words, line by line:
- Line 2 passes the number
1, but Reflect needs an object. It throws aTypeError, and line 4 prints its name. - Line 7 adds an
idproperty.configurable: falselocks it, so it can't be deleted. - Line 8 tries to delete
id. The target is a real object, so nothing throws. The delete fails, so Reflect returnsfalse.
So the two failures mean different things. false means a fair try that didn't work. A TypeError means the call itself was wrong.
Here is the code from the lab as a plain block, so you can paste it into a console.
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));Line 7 returns true even though missing never existed. After the call, the key is not there, and that counts as success.
Receivers: who is asking?
INTERACTIVEFirst, a reminder about getters. A getter is a function that runs when you read a property. Inside the getter, this is the object you read from.
The receiver is the object that becomes this when a getter or setter runs. When you write obj.value, the receiver is obj.
Most of the time, the object that holds the getter is the object you read from. But they can be different:
- With a prototype, the getter can live on the prototype, while you read from a child object.
- With a proxy, the getter is found through the target, while you read from the proxy.
A person at a counter asks, "What does my name badge say?" The clerk passes the question to a back office. If the clerk passes on the exact request slip, the back office reads the right badge. If the clerk writes a new slip with their own name, the back office reads the wrong badge.
- In real life: A clerk answers a question for the person at the counter
- In JavaScript: A getter runs with
thisset to the receiver - In real life: The clerk passes the question on, but writes their own name on the slip
- In JavaScript:
target[key]can run the getter with the wrongthis - In real life: The clerk passes on the exact request slip
- In JavaScript:
Reflect.get(target, key, receiver)keeps the person who asked
Step through a proxy that passes a getter read on. Pick line 12 first: plain target[key], or Reflect.get(target, key, receiver).
Pick line 12, then step. Both choices print the same value here. The steps show what this is inside the getter.
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);The next example makes the difference easy to see. Each proxy writes every key it is asked for into a list named reads.
const reads = [];
const profile = { name: "Asha", get label() { return this.name; } };
const user = Object.create(profile);
const naive = new Proxy(user, { get(target, key) { reads.push(String(key)); return target[key]; } });
console.log(naive.label, reads.join(","));
reads.length = 0;
const forwarded = new Proxy(user, { get(target, key, receiver) { reads.push(String(key)); return Reflect.get(target, key, receiver); } });
console.log(forwarded.label, reads.join(","));In plain words, line by line:
- Line 2:
profilehas anameand alabelgetter that returnsthis.name. - Line 3:
useris a new, empty object whose prototype isprofile. - Line 4: the
naiveproxy writes down each key, then reads it withtarget[key]. - Line 5 prints
Asha label. The getter ran withthisset touser, so itsnameread skipped the proxy. - Line 6 empties the list.
- Line 7: the
forwardedproxy writes down each key, then reads it withReflect.get(target, key, receiver). - Line 8 prints
Asha label,name. The getter ran withthisset to the proxy, so itsnameread went through the trap too.
Asha. Only the right side keeps the proxy as this, so its trap also sees the name read.Here both proxies print the same name. The difference matters when:
- the getter does more work with
this - a setter checks a value using
this - the proxy must see every read
So inside a proxy get trap, write return Reflect.get(target, key, receiver).
Pass each trap on to its Reflect method
STEP THROUGHA proxy trap often does two things: it writes a note in a log, then it does the normal job. The simplest safe way to do the normal job:
- Take every argument the trap gets with
...args. This rest syntax collects all the arguments into one array namedargs. - Pass the same arguments to the Reflect method with the same name, like
Reflect.get(...args). The...here spreads the array back out into separate arguments.
Follow a logging proxy that passes each job on to Reflect. Watch the answer that Reflect.set gives back.
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(", "));count first, then writes it. Each trap writes in the log, then passes the work on to the matching Reflect method.A set trap must return true or false. In strict mode, if it returns false, or forgets to return and so gives undefined, the assignment throws a TypeError. return Reflect.set(...args) does the normal write and returns its true or false in one line.
A trap can also refuse a write. This set trap refuses a negative balance.
const account = { balance: 10 };
const safe = new Proxy(account, {
set(target, key, value, receiver) {
if (key === "balance" && value < 0) return false;
return Reflect.set(target, key, value, receiver);
}
});
console.log(Reflect.set(safe, "balance", 20));
console.log(Reflect.set(safe, "balance", -1));
console.log(account.balance);In plain words, line by line:
- Line 4: if the key is
balanceand the new value is below 0, the trap returnsfalseand writes nothing. - Line 5: any other write goes on to
Reflect.set, which does the write and returnstrue. - Line 8 writes 20. The trap accepts it, so it prints
true. - Line 9 writes -1. The trap refuses it, so it prints
false. - Line 10 prints
20. The refused write never reachedaccount.
Lines 8 and 9 call Reflect.set on the proxy, so a refusal comes back as false, not as an error. The code that made the call can decide what to do next, for example throw its own error.
Reflect.ownKeys lists every key
INTERACTIVEAn object's own keys are the keys stored on the object itself, not on its prototype. Object.keys lists only some of them:
- It lists string keys only, never symbol keys.
- It lists enumerable keys only. An enumerable key is one that shows up in loops and key lists. A key added with
Object.definePropertyis not enumerable unless you ask for it.
Reflect.ownKeys lists every own key, enumerable or not, in this order:
- Number keys: keys that look like whole numbers, such as
"3"and"20", from smallest to biggest. - String keys: all other strings, in the order they were added.
- Symbol keys, in the order they were added.
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 real run always uses Reflect.ownKeys. The buttons only hide parts of its list, so you can compare it with Object.keys. Number keys come first, smallest first.
Reflect.ownKeys lists every own key: number keys, then string keys, then symbols. Object.keys leaves out hidden and the symbol.That fixed order is why an array's indexes always come out as 0, 1, 2, and so on. Symbol keys always come last. If symbol keys are new to you, review the Symbols lesson first.
Sometimes you want only one kind of key. Two Object methods split the list.
const item = { name: "tea" };
Object.defineProperty(item, "hidden", { value: true });
const id = Symbol("id");
item[id] = 1;
console.log(Object.getOwnPropertyNames(item).join(","));
console.log(Object.getOwnPropertySymbols(item).map(String).join(","));In plain words, line by line:
- Line 2 adds
hiddenwithObject.defineProperty, so it is not enumerable. - Lines 3 and 4 add a symbol key.
- Line 5:
Object.getOwnPropertyNameslists every own string key, hidden ones too. It printsname,hidden. - Line 6:
Object.getOwnPropertySymbolslists only symbol keys. It printsSymbol(id).
Use these when you want one kind of key. Use Reflect.ownKeys when you want both kinds, in the standard order.
Reflect.apply and Reflect.construct
INTERACTIVEReflect.apply(fn, thisArg, args) calls a function. It takes three things:
fn: the function to callthisArg: the value forthisinside the functionargs: an array of arguments
It does the same job as fn.apply(thisArg, args), which you met in the call, apply & bind lesson.
Reflect.construct(Target, args, NewTarget) does the job of new Target(...args). The third argument, NewTarget, is optional. When you pass it, two things change:
- Inside the constructor,
new.targetisNewTarget.new.targettells a constructor which functionnewwas used with. - The new object's prototype is
NewTarget.prototype.
The lessons on constructors and new cover these ideas. Two rules still hold:
Targetmust be something you can use withnew. An arrow function can't be, andNewTargetcan't change that.- If
NewTarget.prototypeis not an object, the new object getsObject.prototypeas its prototype instead.
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
- item.createdBySpecial
- Prototype is Special.prototype?true
Reflect.apply calls total with an array of arguments. Reflect.construct runs Base, but its third argument makes new.target and the prototype come from Special.
createdBy to plain text. It doesn't read function names, because tools that shrink code for the web can rename functions.Next, compare Reflect.apply with the older apply method.
function add(a, b) {
return this.base + a + b;
}
const context = { base: 10 };
console.log(Reflect.apply(add, context, [2, 3]));
console.log(add.apply(context, [2, 3]));Both lines call add with this set to context and the arguments 2 and 3. Both print 15, which is 10 + 2 + 3. Reflect puts the function, the this value, and the arguments in three separate slots. That is handy when a helper passes a call on.
Here is the code from the lab again as a plain block.
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 in libraries. A library is code written for other programmers to reuse. Common places:
- proxies that check values before they are saved
- objects that tell the page when their data changes
- test doubles: stand-in objects used in tests
- frameworks that add a small extra step to normal object behavior
Check a value in set. If it is fine, return Reflect.set(...args).
Log each get, set, and deleteProperty, then pass it on to the matching Reflect method.
Try to add a property with Reflect.defineProperty, and check whether it returned false.
Use Reflect.ownKeys when hidden keys or symbol keys must be saved too.
The habit is simple. Inside a proxy trap, when you want the normal behavior, call the matching Reflect method. Then add only the extra step you need.
Outside proxies, the short syntax is often clearer. Write cart[key] for a single read, and delete cart[key] for a single delete. Choose Reflect when a helper or proxy must pass on the exact job and report whether it worked.
Common misconceptions
- "Reflect never throws." Many methods return
falsewhen the job fails. But Reflect still throws aTypeErrorwhen it needs an object and gets something else, like a number. - "Reflect is only for proxies." Proxies are the main use. The function forms also help any helper that gets the job to do as data.
- "Reflect.ownKeys is the same as Object.keys."
Reflect.ownKeysalso lists keys that are not enumerable, and symbol keys.Object.keysleaves both out. - "The receiver is always the target." With inherited getters and proxy traps, the receiver can be the object that was first asked, such as the proxy.
- "Reflect.construct is just
newwithout the word new." Its third argument also setsnew.targetand the new object's prototype. - "A set trap can skip the return." Then it returns
undefined, which counts as failure. In strict mode, the assignment throws aTypeError.
Practice exercises
5 EXERCISESRead the code. What are the two lines it prints?
"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 a TypeError, and the catch block saves its name. Reflect.defineProperty tries the same thing and returns false.
The getter lives on the prototype. What does this print, and why is it the user's name?
"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.
What does the code print? The keys are joined with |.
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 gives 1 and 2 first. Then come first and hidden, in the order they were added. Then comes Symbol(s).
What error name prints? Which one word belongs before Reflect.set to fix it?
"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 does the write but never returns the answer from Reflect.set. So it returns undefined. In strict mode, the assignment treats that as failure and throws a TypeError. The fix is the word return.
What line does Reflect.apply print?
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 array fills the parameters in order: prefix is "total:", a is 2, and b is 5. So the function prints total:7.
Check your understanding
7 QUESTIONSQuestion 1 of 7Which sentence best describes Reflect?
Choose an answer to see the explanation.
Question 2 of 7What does the failed Reflect call 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 does the same job as the
inoperator?Choose an answer to see the explanation.
Question 4 of 7What order does this 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 write
return Reflect.set(...args)in 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 the third argument of Reflect.construct control?
Choose an answer to see the explanation.
Key takeaways
- Reflect does basic object jobs with plain functions.
- It has 13 methods, one for each of the 13 proxy traps.
- Five methods that change an object return
trueorfalsewhen the job works or fails, instead of throwing. - Reflect still throws a
TypeErrorwhen it needs an object and gets something else. - Inside proxy traps, pass
receiveron toReflect.getandReflect.set, sothisstays right. Reflect.ownKeyslists number keys, then other string keys, then symbol keys, hidden keys included.Reflect.constructcan setnew.targetwith its third argument.
Reflect is JavaScript's built-in set of functions for doing basic object jobs, and for passing them on.
Up next: Well-known symbols.