Arrays have map, filter and friends, but objects do not. Instead, the Object constructor has static methods that turn an object into arrays you can work with, and back again. This lesson covers those, then two jobs that trip people up: copying objects properly and stopping them from being changed.
keys, values and entries
Three methods turn an object's own enumerable properties into arrays:
const stock = { apples: 12, pears: 0, plums: 7 };
console.log(Object.keys(stock)); // → ['apples', 'pears', 'plums']
console.log(Object.values(stock)); // → [12, 0, 7]
console.log(Object.entries(stock)); // → [['apples', 12], ['pears', 0], ['plums', 7]]
Each entry is a two-item array of [key, value]. Combine for...of with array destructuring to loop over both at once:
for (const [fruit, count] of Object.entries(stock)) {
console.log(`${fruit}: ${count}`);
}
// → apples: 12
// → pears: 0
// → plums: 7
Once you have an array, all the array methods are available:
const total = Object.values(stock).reduce((sum, n) => sum + n, 0);
console.log(total); // → 19
const outOfStock = Object.keys(stock).filter((fruit) => stock[fruit] === 0);
console.log(outOfStock); // → ['pears']
Prefer these methods to for...in. for...in also walks inherited enumerable properties, which is rarely what you want.
Property order
String keys come back in the order they were added, with one exception: keys that look like whole numbers ('1', '42') come first, in ascending numeric order. If order matters and your keys are numbers, use an array or a Map (lesson 37).
fromEntries: back to an object
Object.fromEntries is the reverse of entries. Together they let you transform an object with array methods:
const prices = { tea: 2.5, coffee: 3, cake: 4.25 };
const withVat = Object.fromEntries(
Object.entries(prices).map(([item, price]) => [item, +(price * 1.2).toFixed(2)])
);
console.log(withVat); // → { tea: 3, coffee: 3.6, cake: 5.1 }
The pattern is: entries, transform the pairs, fromEntries. You can filter in the middle to remove properties too.
Checking for a property
const user = { name: 'Kai', nickname: undefined };
console.log(Object.hasOwn(user, 'nickname')); // → true
console.log(Object.hasOwn(user, 'age')); // → false
console.log(user.age === undefined); // → true
Object.hasOwn tells "set to undefined" apart from "not there at all", and ignores inherited properties. It is supported in all current browsers and Node.js.
Shallow copies
The spread syntax from the last lesson, and the older Object.assign, make shallow copies. The outer object is new, but nested objects are shared:
const original = {
title: 'Quiz',
settings: { shuffle: false },
tags: ['js'],
};
const shallow = { ...original };
shallow.title = 'Copy';
shallow.settings.shuffle = true;
shallow.tags.push('dom');
console.log(original.title); // → Quiz
console.log(original.settings.shuffle); // → true
console.log(original.tags); // → ['js', 'dom']
Changing a top-level property on the copy is safe. Changing anything nested also changes the original, because both objects point at the same nested object. Lesson 40 explains this reference behaviour in depth.
Deep copies with structuredClone
structuredClone copies an object and everything nested inside it:
const deep = structuredClone(original);
deep.settings.shuffle = false;
deep.tags.push('events');
console.log(original.settings.shuffle); // → true (unchanged by deep)
console.log(original.tags); // → ['js', 'dom'] (unchanged by deep)
It is a global function in all current browsers and in Node.js 17 and later. It handles things simpler approaches cannot:
Dateobjects stayDateobjects.Map,Set, typed arrays and many other built-in types are copied.- Circular references, where an object refers back to itself, are preserved.
It cannot copy functions, DOM nodes or class behaviour. Copying an object with a method throws a DataCloneError, and an instance of your own class comes back as a plain object.
structuredClone({ greet() {} });
// DataCloneError: greet() {} could not be cloned.
The exact message differs between browsers and Node.js, but the error name is the same.
The old JSON trick
You will see JSON.parse(JSON.stringify(obj)) in older code. It works for plain data but silently drops undefined and functions, turns dates into strings and fails on circular references. Use structuredClone instead. Lesson 26 covers JSON.
Object.freeze
Object.freeze stops an object from being changed. Properties cannot be added, removed or reassigned:
'use strict';
const config = Object.freeze({ apiVersion: 2, retries: 3 });
config.retries = 10;
// TypeError: Cannot assign to read only property 'retries' of object
In strict mode (modules and classes are always strict) the assignment throws. In sloppy mode it fails silently, which is another reason to stay in strict mode. Use Object.isFrozen(config) to check.
Freeze is shallow too
Only the top level is frozen. Nested objects can still change:
const settings = Object.freeze({ theme: { colour: 'blue' } });
settings.theme.colour = 'red';
console.log(settings.theme.colour); // → red
To freeze everything, walk the object and freeze each nested object:
function deepFreeze(object) {
for (const value of Object.values(object)) {
if (value !== null && typeof value === 'object') {
deepFreeze(value);
}
}
return Object.freeze(object);
}
const locked = deepFreeze({ theme: { colour: 'blue' } });
console.log(Object.isFrozen(locked.theme)); // → true
const and Object.freeze do different jobs. const stops the variable being pointed at a different value. freeze stops the object itself changing. A const object can still have its properties changed.
Choosing the right tool
| You want to | Use |
|---|---|
| Loop over keys and values | Object.entries with for...of |
| Transform an object | entries, map or filter, then fromEntries |
| Check whether a property exists | Object.hasOwn |
| Copy and change top-level values | Spread { ...obj } |
| Copy nested data independently | structuredClone |
| Prevent accidental changes | Object.freeze, or a deep freeze |
Summary
Object.keys,valuesandentriesturn objects into arrays;fromEntriesturns pairs back into an object.- Integer-like keys come first; other keys keep insertion order.
- Spread and
Object.assigncopy shallowly, so nested objects are shared. structuredClonemakes deep copies of plain data, dates, maps and sets, but not functions or DOM nodes.Object.freezeis shallow and throws on writes in strict mode; freeze recursively when you need full immutability.