Objects store named properties. Arrays have methods like .map() and .filter(). Objects do not — because object keys are arbitrary strings, there is no guaranteed order to iterate over. Instead, JavaScript provides utility functions for converting object data into iterable forms.
Object.keys, Object.values, and Object.entries
These three methods extract different views of an object’s data:
const user = { name: "Ada", role: "engineer", active: true };
Object.keys(user);
// ["name", "role", "active"]
Object.values(user);
// ["Ada", "engineer", true]
Object.entries(user);
// [["name", "Ada"], ["role", "engineer"], ["active", true]]
Each returns an array, which means you can use array methods on the result:
// Check if an object has a specific value
Object.values(user).includes("engineer"); // true
// Count properties
Object.keys(user).length; // 3
// Transform entries into a new object
Object.entries(user)
.filter(([key, value]) => typeof value === "string")
.map(([key, value]) => [key, value.toUpperCase()]);
// [["name", "ADA"], ["role", "ENGINEER"]]
Iterating over objects with for...of
Combine Object.entries() with for...of to iterate over key-value pairs:
const settings = { theme: "dark", fontSize: 16, notifications: true };
for (const [key, value] of Object.entries(settings)) {
console.log(`${key}: ${value}`);
}
// theme: dark
// fontSize: 16
// notifications: true
This is cleaner than for...in because Object.entries() returns only the object’s own properties, not inherited ones.
Object.assign merges objects
Object.assign copies properties from source objects into a target object:
const defaults = { theme: "light", fontSize: 14, notifications: true };
const overrides = { theme: "dark", fontSize: 16 };
const settings = Object.assign({}, defaults, overrides);
// { theme: "dark", fontSize: 16, notifications: true }
The first argument is the target. Subsequent arguments are sources whose properties overwrite the target. Passing {} as the first argument avoids mutating defaults.
Spread is the modern replacement
The spread operator provides a more readable way to merge objects:
const settings = { ...defaults, ...overrides };
// { theme: "dark", fontSize: 16, notifications: true }
Spread is equivalent to Object.assign for simple merging. It is more common in modern code because it is shorter and the intent is clearer.
const a = { nested: { x: 1 } };
const b = { ...a };
b.nested.x = 99;
console.log(a.nested.x); // 99 — the nested object is shared
Checking if a property is the object’s own
for...in and Object.keys() only iterate over enumerable own properties. But the in operator checks the entire prototype chain:
const obj = { name: "Ada" };
"name" in obj; // true — own property
"toString" in obj; // true — inherited from Object.prototype
obj.hasOwnProperty("name"); // true
obj.hasOwnProperty("toString"); // false
In modern code, Object.hasOwn() is preferred over hasOwnProperty:
Object.hasOwn(obj, "name"); // true
Object.hasOwn(obj, "toString"); // false
Creating objects with Object.fromEntries
Object.fromEntries is the inverse of Object.entries — it builds an object from key-value pairs:
const pairs = [["name", "Ada"], ["role", "engineer"]];
const user = Object.fromEntries(pairs);
// { name: "Ada", role: "engineer" }
This is useful for converting filtered entries back into an object:
const settings = { theme: "dark", fontSize: 16, notifications: true };
const stringSettings = Object.fromEntries(
Object.entries(settings).filter(([key, value]) => typeof value === "string")
);
// { theme: "dark" }
Object.groupBy groups array items by key (ES2024)
Object.groupBy takes an array and a callback that returns the group key for each element:
const products = [
{ name: "Laptop", category: "electronics" },
{ name: "Phone", category: "electronics" },
{ name: "Book", category: "education" },
];
const byCategory = Object.groupBy(products, (product) => product.category);
// {
// electronics: [{ name: "Laptop", ... }, { name: "Phone", ... }],
// education: [{ name: "Book", ... }],
// }
This replaces a common reduce pattern with a single, readable call.
Object.freeze and Object.seal
JavaScript provides two ways to restrict object mutation:
Object.freeze prevents all changes — no adding, removing, or modifying properties:
const config = Object.freeze({ apiUrl: "https://api.example.com" });
config.apiUrl = "https://other.com"; // silently fails in non-strict mode
config.newProp = "value"; // silently fails
Object.seal prevents adding or removing properties but allows modifying existing ones:
const config = Object.seal({ apiUrl: "https://api.example.com" });
config.apiUrl = "https://other.com"; // works
config.newProp = "value"; // fails
Both are shallow — nested objects can still be modified.
What to carry forward
Object.keys(),Object.values(), andObject.entries()convert object data into arrays- use
for...ofwithObject.entries()to iterate over key-value pairs - spread (
{ ...a, ...b }) merges objects and is the modern default overObject.assign - both spread and
Object.assignare shallow — nested objects are shared Object.hasOwn(obj, key)checks for own properties — preferred overhasOwnPropertyObject.fromEntries()builds an object from key-value pairsObject.groupBy()groups array items by a key function (ES2024)Object.freeze()andObject.seal()restrict mutation but are shallow and rarely needed
Object operations complement array methods. The next lesson covers destructuring and spread/rest in more depth — the patterns that make extracting and combining data concise.