Enumerability & ownership
Learn which JavaScript properties are own, inherited, enumerable, hidden, string, or symbol keys, and which loop or method can see each one.
- 01Answer the two visibility questionsSay if a property is stored in the object or in its prototype, and if it shows up in lists of keys.
- 02Pick the right tool for listing keysCompare
Object.keys,for...in,Reflect.ownKeys, and the tools for string keys and symbol keys. - 03Predict the order of keysUse the three groups: number keys, then other strings, then symbols.
Two visibility questions
An object can hold more properties than a quick loop shows you. A key is the name of a property, like name in user.name. Some tools list an object's keys, and each tool shows a different set.
To predict what a tool shows, ask two questions about each property:
- Ownership: is the property stored in this object itself? Or is it stored in another object that this one looks at next (an inherited property)?
- Enumerability: does the property show up in lists of keys? Or is it left out of them (a hidden property)?
Think of a phone book. Some records belong to your own house. Some belong to your parents' house down the street. Each record is either printed in the phone book or kept out of it. Different clerks read different shelves: one reads only your house, one reads both houses, one reads every record.
- In real life: A record kept at your own house
- In JavaScript: An own property
- In real life: A record kept at your parents' house
- In JavaScript: An inherited property
- In real life: Printed in the phone book
- In JavaScript:
enumerable: true - In real life: Kept out of the phone book
- In JavaScript:
enumerable: false
You have already met each piece on its own: for...in, Object.keys, symbols, the prototype chain, and the enumerable flag. This lesson puts them together. By the end, you can say which tool sees which key.
Own vs inherited
STEP THROUGHAn own property is stored in the object itself. An inherited property is stored in the object's prototype. The prototype is another object that JavaScript looks in next when the first object doesn't have the key. The prototype can have its own prototype, and so on; that line of objects is the prototype chain.
Reading user.name works the same for both kinds. But many tools that list keys care about the difference.
Two tools in the code below check for a key:
Object.hasOwn(user, "name")givestrueonly ifuseritself storesname."name" in usergivestrueif JavaScript can findnameinuseror in any prototype.
Object.create(prototype) on line 2 makes a new, empty object whose prototype is prototype. Guess which checks print true, then step through.
Guess which checks give true. Then step through: stored here, or found in the prototype?
script
const user = Object.create(prototype);user.name = "Ada"; console.log(Object.hasOwn(user, "name"));console.log(Object.hasOwn(user, "inheritedRole"));console.log("inheritedRole" in user);name is stored in user, so it is an own property. inheritedRole is stored in prototype; user only finds it by looking there next.Object.hasOwn(obj, key) asks: "Is it stored right here?" The in operator asks: "Can JavaScript find it here or in a prototype?" Both are useful. They answer different questions.
Enumerable vs non-enumerable
JavaScript keeps a few settings for every property. Together they are called the property's descriptor. One setting is enumerable. It is true or false.
- An enumerable property shows up in lists of keys.
- A non-enumerable property is left out of those lists. This lesson also calls it a hidden property.
How you make a property decides the setting:
- Writing
user.name = "Ada"or{ name: "Ada" }makes an enumerable property. Object.defineProperty(obj, key, settings)makes a property with the settings you give. If you don't sayenumerable: true, the property is hidden.
Hidden does not mean secret. Code that knows the name can still read the property. Only tools that list keys skip it, such as Object.keys, for...in, and JSON.stringify.
const user = { name: "Asha" };Object.defineProperty(user, "password", { value: "secret" }); console.log(user.password);console.log("password" in user);console.log(user.propertyIsEnumerable("password"));Line 2 adds password with Object.defineProperty, so it is hidden. Then:
- Line 4 reads
user.passwordby name. It prints"secret". - Line 5 asks if JavaScript can find
password. Theincheck doesn't care about lists, so it printstrue. - Line 6 asks
propertyIsEnumerable. It givestrueonly for an own property that shows up in lists.passwordis hidden, so it printsfalse.
password is really there: you can read it, and in finds it. It is only marked as hidden, so propertyIsEnumerable says false.You can also read the descriptor itself. Object.getOwnPropertyDescriptor(obj, key) gives back an object with all the settings of one property.
const user = { name: "Asha" };Object.defineProperty(user, "password", { value: "secret" });const password = Object.getOwnPropertyDescriptor(user, "password"); console.log(password.enumerable);console.log(Object.hasOwn(user, "password"));Line 3 gets the descriptor of password and keeps it in a variable. Line 5 prints its enumerable setting: false. Line 6 prints true, because password is still stored in user itself. Hidden and own can both be true at once.
The two questions don't depend on each other. In the table, each row is one kind of key. Each column says whether it is listed or hidden.
| Kind of key | When it shows up in lists | When it is hidden |
|---|---|---|
| Own property | obj.name = "Ada" makes a key that Object.keys lists. | defineProperty(obj, "secret", { value: 1 }) makes a key that Object.keys skips. |
| Inherited property | for...in lists it. | Reading it by name works, but no list shows it. |
| String key | The common lists show it. | getOwnPropertyNames still finds it. |
| Symbol key | Spread and Object.assign copy it. | getOwnPropertySymbols and Reflect.ownKeys still find it. |
Object.keys vs for…in vs Reflect.ownKeys
INTERACTIVEA common mistake is to think every key tool is a bigger or smaller Object.keys. It isn't. Each tool has its own rule. The rule answers three questions: own or inherited, listed or hidden, string or symbol.
Object.keys,Object.values, andObject.entriesread own keys that are listed and are strings.for...inreads listed string keys of the object, then listed string keys of its prototypes.JSON.stringifyturns an object into JSON text. It uses own listed string keys and skips symbols.Object.assignand spread ({ ...obj }) copy own listed keys, both strings and symbols.Reflect.ownKeysreads every own key: strings and symbols, listed or hidden.
The playground runs every tool on the same object, in your browser. Each card shows what one tool found.
const shown = Symbol("shown");const hiddenSymbol = Symbol("hidden");const proto = { inheritedEnum: "from prototype" };Object.defineProperty(proto, "inheritedHidden", { value: "secret" });const item = Object.create(proto);item[2] = "integer-like";item.title = "Guide";Object.defineProperty(item, "secret", { value: 42 });item[shown] = "symbol value";Object.defineProperty(item, hiddenSymbol, { value: "symbol secret" });The results appear once the page has loaded in your browser.
Running the tools in your browser…
Object.values and Object.entries don't have their own rule. They take the same keys as Object.keys. Then they give back the values, or key-and-value pairs, for those keys.
Copying and turning into JSON are close, but not the same. Spread and Object.assign keep listed symbol keys. JSON.stringify drops every symbol, because JSON text can only have string keys. So pick the tool by what you are making: a copy of the object, or JSON text.
const id = Symbol("id");const source = { name: "Asha" };Object.defineProperty(source, "secret", { value: 7 });source[id] = 42; const copy = Object.assign({}, source);console.log(Object.keys(copy));console.log(Object.getOwnPropertySymbols(copy).length);console.log("secret" in copy);Lines 1 to 4 make a source object with three keys:
name: a listed string key.secret: a hidden string key.id: a listed symbol key.
Line 6 copies source into a new empty object. Line 7 prints [ 'name' ]. Line 8 prints 1, so the symbol was copied. Line 9 prints false, so the hidden secret was not copied.
getOwnPropertyNames & getOwnPropertySymbols
A key is either a string or a symbol. A symbol is a unique value made with Symbol("label"). Many tools only look at string keys. Two tools find hidden keys too, one for each kind:
Object.getOwnPropertyNames(obj)gives every own string key, listed or hidden.Object.getOwnPropertySymbols(obj)gives every own symbol key, listed or hidden.
Use both when you need both kinds. Or use Reflect.ownKeys(obj), which gives every own key in one list.
A symbol key can be listed or hidden, just like a string key. Two things decide which tools see it:
- Being a symbol decides which tools can see it at all.
- The
enumerablesetting decides whether spread andObject.assigncopy it.
Your house keeps its records in two drawers: one for name cards, one for badge cards. One clerk reads only the printed phone book. Another clerk opens the name drawer. Another opens the badge drawer. The last clerk empties both drawers onto the desk and reads every card.
- In real life: The printed phone book
- In JavaScript:
Object.keys - In real life: The drawer of name cards
- In JavaScript:
Object.getOwnPropertyNames - In real life: The drawer of badge cards
- In JavaScript:
Object.getOwnPropertySymbols - In real life: Both drawers, emptied onto the desk
- In JavaScript:
Reflect.ownKeys
const visible = Symbol("visible");const hidden = Symbol("hidden");const user = { name: "Asha" };user[visible] = "tag";Object.defineProperty(user, "password", { value: "secret" });Object.defineProperty(user, hidden, { value: "note" }); console.log(Object.getOwnPropertyNames(user));console.log(Object.getOwnPropertySymbols(user).length);console.log(Reflect.ownKeys(user).length);Lines 1 and 2 make two symbols. Lines 3 to 6 give user four keys:
- Line 3:
name, a listed string key. - Line 4: the symbol
visible, a listed symbol key. - Line 5:
password, a hidden string key. - Line 6: the symbol
hidden, a hidden symbol key.
Line 8 prints both string keys. Line 9 prints 2, the number of symbol keys. Line 10 prints 4: Reflect.ownKeys found all of them.
Reflect.ownKeys finds all four.- Own enumerable string key
- Own non-enumerable string key
- Inherited enumerable string key
- Own enumerable symbol key
- Own enumerable key
"0"on an array - Array
length
Put each kind of key in a group. Object.keys lists a key only if all three are true: it is own, it is enumerable, and it is a string.
Property order rules
STEP THROUGHTools that list own keys, like Object.keys and Reflect.ownKeys, follow a fixed order. It is not always the order you added the keys. The keys come in three groups:
- Number keys first, smallest number first.
- Other string keys next, in the order they were added.
- Symbol keys last, in the order they were added.
A number key here is a string that is a whole number of 0 or more, written the normal way, such as "2" or "10". "01" starts with an extra zero, and "-1" is below zero. So both stay in the group of other strings.
Step through the three groups: number keys, other strings, symbols.
script
const b = Symbol("b");const record = { "10": "ten", "2": "two" };record.name = "Ada";record.city = "Pune";record[b] = "second symbol";record[a] = "first symbol"; console.log(Reflect.ownKeys(record));"2" comes before "10" because number keys go smallest first. name was added before city, and symbol b before symbol a, so they keep that order.Built-ins hide their housekeeping
INTERACTIVEBuilt-in objects, like arrays, use the same enumerable setting.
- Array positions are listed. So
Object.keys(["a", "b"])gives["0", "1"]. - An array's
lengthis an own property too, but it is hidden. SoObject.keysandfor...indon't show it.
Class methods work the same way. A method is stored in the class's prototype, not in each object, and it is hidden. So a loop over an object made by the class shows only its data, not its method names.
On line 8, new Counter() makes an object from the class. The results run in your browser.
class Counter { increment() { return 1; }} const array = ["a", "b"];const counter = new Counter(); console.log(Object.keys(array));console.log("length" in array);console.log(Object.keys(counter));console.log(Object.getOwnPropertyNames(Counter.prototype));The results appear once the page has loaded.
Arrays and classes are normal objects. Some of their properties are set to hidden. Your browser ran this code.
length and a class's methods are hidden.Where you’ll use this
These rules matter whenever you copy an object, turn it into JSON, or loop over its keys:
- Use
Object.keysfor plain data. - Use
Object.hasOwninsidefor...inwhen inherited keys would be a bug. - Use descriptors when a copy must keep hidden properties, symbols, getters, setters, or settings.
Object.defineProperties({}, Object.getOwnPropertyDescriptors(source)) copies every own property with all its settings. Use it instead of spread when hidden properties or symbols matter.
const user = { name: "Asha" };Object.defineProperty(user, "sessionToken", { value: "abc123" });const publicProfile = { ...user }; console.log(Object.keys(publicProfile));console.log(user.sessionToken);Line 2 adds a hidden sessionToken. The spread on line 3 copies only own listed keys. So line 5 shows that publicProfile has name but no token. Line 6 reads the token from the original by name, and prints "abc123".
This is handy for making a copy to show on screen. It does not keep anything safe. Never put a real secret in an object that runs in the browser.
A common use: leave a property out of JSON without deleting it. Make the property hidden. Code that knows the name can still read it, but JSON.stringify leaves it out.
const profile = { name: "Asha", theme: "dark" };Object.defineProperty(profile, "lastViewedPage", { value: "/home" }); console.log(JSON.stringify(profile));console.log(profile.lastViewedPage);Line 1 makes the profile. Line 2 adds lastViewedPage, which only the page itself needs, as a hidden property. Line 4 prints JSON with only name and theme. Line 5 still reads "/home". This changes what goes into the JSON. It does not protect a password or token.
Common misconceptions
“Object.keys shows all properties.”
It shows only own keys that are enumerable and are strings. It skips symbol keys, hidden keys, and inherited keys.
“for...in is the same as Object.keys.”
for...in also shows enumerable string keys from prototypes. Check each key with Object.hasOwn when you want only the object's own keys.
“Non-enumerable means private.”
A hidden property is only left out of some lists. Any code that knows its name can still read it. It keeps nothing safe.
“propertyIsEnumerable checks whether JavaScript can find the key.”
It asks a smaller question. It gives true only for an own property that is enumerable. For an inherited property, in gives true, but this method gives false.
“Symbols are invisible.”
Many tools that read string keys skip symbols. But Object.getOwnPropertySymbols and Reflect.ownKeys find them.
“Object key order is random.”
Tools that list own keys follow a fixed order: number keys, then other strings, then symbols. You stepped through it above.
Each row of this table is one tool. Read across to see which keys it includes.
| Tool | Only own keys? | Only enumerable keys? | Which kind of keys? |
|---|---|---|---|
Object.keys | Yes, it skips inherited keys. | Yes, it skips hidden keys. | Strings only. |
for...in | No, it adds inherited keys. | Yes, it skips hidden keys. | Strings only. |
Object.getOwnPropertyNames | Yes, it skips inherited keys. | No, it finds hidden keys too. | Strings only. |
Object.getOwnPropertySymbols | Yes, it skips inherited keys. | No, it finds hidden keys too. | Symbols only. |
Reflect.ownKeys | Yes, it skips inherited keys. | No, it finds hidden keys too. | Strings and symbols. |
Practice: predict the listing
5 EXERCISESPredict the two printed lines. Type the first line, a slash, then the second line.
const proto = { inherited: 1 };
const box = Object.create(proto);
box.visible = 2;
Object.defineProperty(box, "hidden", { value: 3 });
console.log(Object.keys(box).join(","));
const seen = [];
for (const key in box) seen.push(key);
console.log(seen.join(","));visible is own and enumerable, so both tools list it. inherited is enumerable but stored in proto, so only for...in adds it. hidden was made with defineProperty, so neither tool lists it.
Which pair of tools copies the hidden string property and the symbol property?
const id = Symbol("id");
const source = { name: "Ada" };
Object.defineProperty(source, "secret", { value: 7 });
source[id] = 42;
const copy = Object.defineProperties({}, Object.getOwnPropertyDescriptors(source));
console.log(Reflect.ownKeys(copy).map(String).join(","));
console.log(copy.secret);const copy = Object.defineProperties({}, Object.getOwnPropertyDescriptors(source));Descriptors keep the enumerable: false setting and include symbol keys. So the copy keeps secret and the symbol property.
Why does the loop print both keys?
const proto = { inherited: "yes" };
const child = Object.create(proto);
child.own = "yes";
const seen = [];
for (const key in child) seen.push(key);
console.log(seen.join(","));inherited is stored in proto, and it is enumerable. for...in lists enumerable string keys of the object and of its prototypes.
Predict the exact line that Reflect.ownKeys prints.
const s = Symbol("s");
const item = { "10": "ten", "2": "two", apple: 1 };
item["01"] = "leading";
item[s] = "symbol";
console.log(Reflect.ownKeys(item).map(String).join("|"));The order is 2, 10, apple, 01, then Symbol(s). 01 starts with an extra zero, so it is not a number key. It joins the other strings, after apple.
What JSON text prints after password becomes hidden?
const user = { name: "Ada", password: "secret" };
Object.defineProperty(user, "password", { enumerable: false });
console.log(JSON.stringify(user));
console.log("password" in user);Object.defineProperty(user, "password", { enumerable: false });You can still read user.password, and "password" in user is still true. But JSON leaves it out.
Quiz: check your understanding
7 QUESTIONSEvery choice explains itself, including the wrong ones.
Question 1 of 7Which two questions describe a property’s visibility?
Choose an answer to see the explanation.
Question 2 of 7What does Object.keys print for the child object?
Read the code, then predictconst proto = { inherited: 1 }; const obj = Object.create(proto); obj.own = 2; console.log(Object.keys(obj).join(","));Choose an answer to see the explanation.
Question 3 of 7Which tool gives every own string key, listed or hidden?
Choose an answer to see the explanation.
Question 4 of 7What does Object.keys print when the object also has a symbol?
Read the code, then predictconst s = Symbol("id"); const obj = { name: "Ada" }; obj[s] = 1; console.log(Object.keys(obj).join(","));Choose an answer to see the explanation.
Question 5 of 7Which tool sees every own key: strings and symbols, listed and hidden?
Choose an answer to see the explanation.
Question 6 of 7What order does Reflect.ownKeys print for these string keys?
Read the code, then predictconst obj = { "10": "ten", "2": "two", a: 1 }; obj.b = 2; console.log(Reflect.ownKeys(obj).join("|"));Choose an answer to see the explanation.
Question 7 of 7How can you leave a property out of
JSON.stringifywithout deleting it?Choose an answer to see the explanation.
Key takeaways
- An own property is stored in the object itself. An inherited property is stored in its prototype.
- An enumerable property shows up in lists of keys. A hidden (non-enumerable) property is left out, but it still exists.
Object.keyslists own keys that are enumerable and are strings.for...inlists enumerable string keys, including inherited ones.Reflect.ownKeyslists every own key: strings and symbols, listed or hidden.- Own keys come in a fixed order: number keys, then other strings, then symbols.
Remember the one-liner.
Pick a key tool by three questions: own or inherited, listed or hidden, string or symbol.
Up next: try, catch & finally.