Each enumeration method has different behavior around own properties, inherited properties, and enumerability:
const parent = { inherited: true };
const obj = Object.create(parent); // obj's prototype is parent
obj.a = 1;
obj.b = 2;
Object.defineProperty(obj, 'hidden', { value: 3, enumerable: false });
// for...in — own + inherited, enumerable only
for (const k in obj) console.log(k); // 'a', 'b', 'inherited'
// Object.keys — own properties, enumerable only ← most common
Object.keys(obj); // ['a', 'b']
// Object.values — own, enumerable, values
Object.values(obj); // [1, 2]
// Object.entries — own, enumerable, [key, value] pairs
Object.entries(obj); // [['a', 1], ['b', 2]]
// Object.getOwnPropertyNames — own, ALL (including non-enumerable)
Object.getOwnPropertyNames(obj); // ['a', 'b', 'hidden']
// Check if own property
obj.hasOwnProperty('a'); // true
obj.hasOwnProperty('inherited'); // false
Object.hasOwn(obj, 'a'); // ES2022 — preferred over hasOwnProperty
💡 Use Object.keys/values/entries in modern code — they only return own enumerable properties. Use for...in only if you explicitly need inherited properties (rare).