JAVASCRIPT / OBJECTS AND PROPERTY MECHANICS
Iterating own properties without prototype noise
Iterate only an object's own properties, and choose deliberately between Object.keys, getOwnPropertyNames, Reflect.ownKeys, and a guarded for...in.
What you will learn
- Iterate own enumerable data with Object.keys, Object.values, or Object.entries
- Explain why for...in leaks inherited keys and when Object.hasOwn is the right guard
- Reach hidden keys with Object.getOwnPropertyNames and Reflect.ownKeys
- Use Object.create(null) for lookup tables built from untrusted key names
Understanding Iterating own properties without prototype noise
Any object you loop over is really the head of a chain, and two independent flags decide whether a key shows up: whether the key lives on the object itself, and whether its enumerable attribute is true. for...in checks only enumerability, and it keeps walking up the chain until the chain ends. Object.keys checks both conditions and never leaves the object. Same object, two different key lists, and neither one is buggy.
Plain object literals feel safe under for...in only because everything on Object.prototype (toString, valueOf, hasOwnProperty) was defined with enumerable: false. The noise appears the moment an enumerable property lands on a prototype, which is exactly what Object.create(defaults) and Ctor.prototype.method = fn produce. Methods written inside a class body are non-enumerable, which is why class instances usually look clean and hand-built prototypes do not. So the apparent safety of for...in is a property of how the builtins were defined, not a guarantee about your data.
There is a third axis besides ownership and enumerability: string keys versus symbol keys. Object.keys, values and entries give own enumerable string keys; Object.getOwnPropertyNames drops the enumerable filter; Object.getOwnPropertySymbols returns only symbols; Reflect.ownKeys returns every own key. Choose by the question you are asking: "what data did the caller put here" is Object.entries, while "what would a faithful clone need" is Reflect.ownKeys. If you must use for...in, guard the body with Object.hasOwn(obj, key).
The guard itself has a trap worth knowing before you write it.
const defaults = { theme: "light", lang: "en" };
const settings = Object.create(defaults);
settings.theme = "dark";
settings.fontSize = 14;
const seen = [];
for (const key in settings) seen.push(key);
console.log("for...in :", seen);
console.log("Object.keys:", Object.keys(settings));
const own = [];
for (const key in settings) {
if (Object.hasOwn(settings, key)) own.push(key);
}
console.log("guarded :", own);
console.log(settings.lang, Object.hasOwn(settings, "lang"), "lang" in settings);for...in filters keys by enumerability alone while Object.keys also filters by ownership, and that missing ownership filter is precisely where prototype noise comes from.
Worked examples
Four views of the same object
Shows how the own-key helpers differ on enumerability and on symbol keys.
const id = Symbol("id");
const record = { name: "ada" };
Object.defineProperty(record, "internal", { value: 1, enumerable: false });
record[id] = 7;
console.log(Object.keys(record));
console.log(Object.getOwnPropertyNames(record));
console.log(Object.getOwnPropertySymbols(record));
console.log(Reflect.ownKeys(record));
console.log(Object.entries(record));Example explained
Line 1Object.keys applies both filters, own and enumerable, so only name survives.
Line 2getOwnPropertyNames keeps the ownership filter but drops the enumerable one, revealing internal.
Line 3No string-key helper will ever report Symbol(id); getOwnPropertySymbols is the only route to it.
Line 4Reflect.ownKeys concatenates them: integer-like keys, then strings in insertion order, then symbols.
Why obj.hasOwnProperty(key) is the fragile spelling
Demonstrates the two ways the inherited hasOwnProperty method disappears at the call site.
const counts = Object.create(null);
counts.banana = 2;
try {
counts.hasOwnProperty("banana");
} catch (err) {
console.log(err.constructor.name);
}
console.log(Object.hasOwn(counts, "banana"));
console.log(Object.keys(counts));
const raw = JSON.parse('{"hasOwnProperty": 0, "apple": 1}');
try {
raw.hasOwnProperty("apple");
} catch (err) {
console.log(err.constructor.name);
}
console.log(Object.prototype.hasOwnProperty.call(raw, "apple"));Example explained
Line 1counts has no prototype at all, so counts.hasOwnProperty is undefined and calling it throws.
Line 2Object.hasOwn does not look the method up on the object, so it works on prototype-less maps.
Line 3The parsed JSON shadows hasOwnProperty with the number 0, so the call throws for a different reason.
Line 4Object.prototype.hasOwnProperty.call bypasses the shadowing by fetching the function directly.
Arrays have own properties too
Shows that filtering out the prototype does not filter out extra own keys you added yourself.
const scores = [10, 20, 30];
scores.label = "week1";
const kinds = [];
for (const k in scores) kinds.push(typeof k + ":" + k);
console.log(kinds);
console.log(Object.keys(scores));
let total = 0;
for (const v of scores) total += v;
console.log(total);Example explained
Line 1for...in yields property keys, and property keys are strings, so the index 0 arrives as '0'.
Line 2label is an own enumerable property, so Object.keys reports it alongside the indices.
Line 3for...of asks the array iterator for elements instead of keys, which is why label never appears.
Line 4total stays numeric because no string value ever reaches the += operator.
Important notes
Object.hasOwn is ES2022; on older runtimes use Object.prototype.hasOwnProperty.call(obj, key), which has identical semantics.
Own string keys that look like array indices are always listed first in ascending numeric order, before the insertion-ordered ones; the order in which for...in visits inherited keys is not fully specified, so never rely on it.
Common mistakes
Calling obj.hasOwnProperty(key) on parsed JSON or an Object.create(null) map: the method is shadowed or absent and the call throws a TypeError instead of returning false.
Using key in obj as an ownership test: it returns true for toString, constructor and every other inherited key, so validation code accepts fields the caller never sent.
Treating Object.keys as "all own properties": non-enumerable and symbol-keyed properties are dropped silently, so hand-written copy loops lose them with no error to debug.
Try it yourself
Change, predict, then run
Create const proto = { role: "user" } and const u = Object.create(proto) with u.name = "kim", then add a non-enumerable token via Object.defineProperty and one symbol key. Write down your predicted output for for...in, Object.keys, Object.getOwnPropertyNames and Reflect.ownKeys before running all four.
Open the JavaScript workspaceCheck your understanding
For const o = { a: 1 }, the expression "toString" in o is true, yet for (const k in o) never yields toString. What explains that?
- Object.prototype.toString is defined with enumerable: false, and for...in visits only enumerable keys
- for...in visits only own properties, so inherited keys like toString are skipped
- toString is stored under a symbol key, and for...in skips symbol-keyed properties
- for...in skips any property whose value is a function
Show answer
for...in does traverse the prototype chain; it filters on the enumerable attribute alone, and the builtins on Object.prototype were all defined as non-enumerable. That rules out option 2, the tempting one: if for...in really visited only own keys you would never need an Object.hasOwn guard, and setting Object.prototype.tag = 1 would not make tag appear in every for...in loop, which it does.