Querying object properties
There are four built-in ways to query a property of an object. They all support both string and symbol keys. The following table summarizes when each method returns true.
| Enumerable, own | Enumerable, inherited | Non-enumerable, own | Non-enumerable, inherited | |
|---|---|---|---|---|
propertyIsEnumerable() |
true ✅ |
false ❌ |
false ❌ |
false ❌ |
hasOwnProperty() |
true ✅ |
false ❌ |
true ✅ |
false ❌ |
Object.hasOwn() |
true ✅ |
false ❌ |
true ✅ |
false ❌ |
in |
true ✅ |
true ✅ |
true ✅ |
true ✅ |
Traversing object properties
There are many methods in JavaScript that traverse a group of properties of an object. Sometimes, these properties are returned as an array; sometimes, they are iterated one-by-one in a loop; sometimes, they are used for constructing or mutating another object. The following table summarizes when a property may be visited.
Methods that only visit string properties or only symbol properties will have an extra note. ✅ means a property of this type will be visited; ❌ means it will not.
| Enumerable, own | Enumerable, inherited | Non-enumerable, own | Non-enumerable, inherited | |
|---|---|---|---|---|
Object.keysObject.valuesObject.entries |
✅ (strings) |
❌ | ❌ | ❌ |
Object.getOwnPropertyNames |
✅ (strings) |
❌ | ✅ (strings) |
❌ |
Object.getOwnPropertySymbols |
✅ (symbols) |
❌ | ✅ (symbols) |
❌ |
Object.getOwnPropertyDescriptors |
✅ | ❌ | ✅ | ❌ |
Reflect.ownKeys |
✅ | ❌ | ✅ | ❌ |
for...in |
✅ (strings) |
✅ (strings) |
❌ | ❌ |
Object.assign(After the first parameter) |
✅ | ❌ | ❌ | ❌ |
| Object spread | ✅ | ❌ | ❌ | ❌ |
Obtaining properties by enumerability/ownership
Note that this is not the most efficient algorithm for all cases, but useful for a quick demonstration.
- Detection can occur by
SimplePropertyRetriever.theGetMethodYouWant(obj).includes(prop) - Iteration can occur by
SimplePropertyRetriever.theGetMethodYouWant(obj).forEach((value, prop) => {});(or usefilter(),map(), etc.)
js
const SimplePropertyRetriever = {
getOwnEnumProps(obj) {
return this._getPropertyNames(obj, true, false, this._enumerable);
// Or could use for...in filtered with Object.hasOwn or just this: return Object.keys(obj);
},
getOwnNonEnumProps(obj) {
return this._getPropertyNames(obj, true, false, this._notEnumerable);
},
getOwnProps(obj) {
return this._getPropertyNames(
obj,
true,
false,
this._enumerableAndNotEnumerable,
);
// Or just use: return Object.getOwnPropertyNames(obj);
},
getPrototypeEnumProps(obj) {
return this._getPropertyNames(obj, false, true, this._enumerable);
},
getPrototypeNonEnumProps(obj) {
return this._getPropertyNames(obj, false, true, this._notEnumerable);
},
getPrototypeProps(obj) {
return this._getPropertyNames(
obj,
false,
true,
this._enumerableAndNotEnumerable,
);
},
getOwnAndPrototypeEnumProps(obj) {
return this._getPropertyNames(obj, true, true, this._enumerable);
// Or could use unfiltered for...in
},
getOwnAndPrototypeNonEnumProps(obj) {
return this._getPropertyNames(obj, true, true, this._notEnumerable);
},
getOwnAndPrototypeEnumAndNonEnumProps(obj) {
return this._getPropertyNames(
obj,
true,
true,
this._enumerableAndNotEnumerable,
);
},
// Private static property checker callbacks
_enumerable(obj, prop) {
return Object.prototype.propertyIsEnumerable.call(obj, prop);
},
_notEnumerable(obj, prop) {
return !Object.prototype.propertyIsEnumerable.call(obj, prop);
},
_enumerableAndNotEnumerable(obj, prop) {
return true;
},
// Inspired by http://stackoverflow.com/a/8024294/271577
_getPropertyNames(obj, iterateSelf, iteratePrototype, shouldInclude) {
const props = [];
do {
if (iterateSelf) {
Object.getOwnPropertyNames(obj).forEach((prop) => {
if (props.indexOf(prop) === -1 && shouldInclude(obj, prop)) {
props.push(prop);
}
});
}
if (!iteratePrototype) {
break;
}
iterateSelf = true;
obj = Object.getPrototypeOf(obj);
} while (obj);
return props;
},
};