You already know how to create objects and read their properties. Real programs need more: loop over every property, turn an object into a list and back, merge default settings with user choices, lock an object so it can't be changed, and copy an object without the copy secretly sharing data with the original.
JavaScript's built-in Object helpers handle all of this. Object.keys, Object.values and Object.entries turn an object into arrays you can loop over and use with map and filter. Object.fromEntries turns them back. Object.assign and the spread syntax merge objects, and Object.freeze makes them read-only.
You'll also learn getters and setters, which look like ordinary properties but run code when read or written, and the most important idea of all: the difference between a shallow copy and a deep copy. Many confusing bugs come from changing a "copy" and accidentally changing the original too. structuredClone fixes that.
Turning objects into arrays and back
| Method | Returns | For { a: 1, b: 2 } |
|---|---|---|
Object.keys(obj) | Array of keys | ["a", "b"] |
Object.values(obj) | Array of values | [1, 2] |
Object.entries(obj) | Array of [key, value] pairs | [["a", 1], ["b", 2]] |
Object.fromEntries(pairs) | An object from pairs | { a: 1, b: 2 } |
The pattern entries, then an array method, then fromEntries transforms an object in one line:
const prices = { tea: 2, cake: 3 };
const withTax = Object.fromEntries(
Object.entries(prices).map(([item, p]) => [item, p * 1.2])
);Merging, checking and locking
| Method | Does |
|---|---|
{ ...defaults, ...options } | Merges into a new object; later properties win |
Object.assign(target, source) | Copies properties into target (changes it) |
Object.hasOwn(obj, "key") | Is the property on the object itself? |
"key" in obj | Is it on the object or its prototype? |
delete obj.key | Removes a property |
Object.freeze(obj) | Makes properties read-only; changes are ignored, or throw in strict mode |
Object.isFrozen(obj) | Checks if an object is frozen |
Object.freeze is shallow: nested objects inside a frozen object can still change.
Getters and setters
const user = {
first: "Ana",
last: "Silva",
get fullName() { return `${this.first} ${this.last}`; },
set fullName(value) { [this.first, this.last] = value.split(" "); },
};
user.fullName; // "Ana Silva", no brackets
user.fullName = "Ben Cole"; // runs the setter- A getter (
get) runs when the property is read. Use it for values calculated from other properties, so they're never out of date. - A setter (
set) runs when the property is assigned. Use it to validate or convert values. - You read and write them like normal properties, without calling them. They work the same way in classes.
Shallow vs deep copies
| Technique | Copies nested objects? |
|---|---|
const b = a | No: b is the same object |
{ ...a }, Object.assign({}, a) | Top level only (shallow) |
structuredClone(a) | Yes, every level (deep) |
JSON.parse(JSON.stringify(a)) | Yes, but loses dates, functions, undefined, Maps and Sets |
With a shallow copy, nested objects and arrays are still shared, so changing copy.address.city also changes the original. structuredClone is built into every modern browser and copies dates, Maps, Sets and nested data correctly. It can't copy functions or DOM elements.
JavaScript Object Methods Syntax
Object helpers
Object.keys(obj); Object.values(obj); Object.entries(obj);
Object.fromEntries([["a", 1]]);
const merged = { ...defaults, ...settings };
Object.hasOwn(obj, "id");
Object.freeze(config);
const copy = structuredClone(original);
const temp = { c: 20, get f() { return this.c * 9 / 5 + 32; } };JavaScript Object Methods Summary
Object.keys,valuesandentriesturn objects into arrays;fromEntriesturns pairs back.- Spread
{ ...a, ...b }merges objects; later properties win. Object.freezemakes an object read-only, but only one level deep.- Getters calculate values when read; setters run code when written.
- Spread copies are shallow;
structuredClonemakes a deep copy.
JavaScript Object Methods Examples
Each example runs in the page. Edit the code and press Run to see what changes.
Looping and transforming objects
const scores = { maths: 72, english: 88, science: 65, art: 91 };
for (const [subject, score] of Object.entries(scores)) {
console.log(`${subject.padEnd(8)} ${score}`);
}
const total = Object.values(scores).reduce((sum, s) => sum + s, 0);
console.log("Average:", total / Object.keys(scores).length);
const passed = Object.fromEntries(
Object.entries(scores).filter(([, score]) => score >= 70)
);
console.log("Passed:", passed);Line-by-line explanation
- Line 3
entriesgives each subject and score together. - Lines 7 to 8
valuesandkeysgive arrays you can total and count. - Lines 10 to 12Filter the pairs, then rebuild an object with only the passing subjects.
Defaults, freezing and getters
const defaults = Object.freeze({ theme: "light", fontSize: 16, sound: true });
const saved = { fontSize: 18 };
const settings = { ...defaults, ...saved };
console.log(settings);
defaults.theme = "dark";
console.log("Frozen still light:", defaults.theme, Object.isFrozen(defaults));
const temperature = {
celsius: 20,
get fahrenheit() { return this.celsius * 9 / 5 + 32; },
set fahrenheit(f) { this.celsius = Math.round((f - 32) * 5 / 9); },
};
console.log(temperature.fahrenheit);
temperature.fahrenheit = 212;
console.log(temperature.celsius);Line-by-line explanation
- Line 1Defaults are frozen so nothing can change them.
- Line 4The saved font size overrides the default; the rest is kept.
- Line 7Assigning to a frozen object is ignored (or throws in strict mode).
- Line 12The getter calculates Fahrenheit whenever it's read.
- Line 13The setter converts a Fahrenheit value back to Celsius.
Shallow vs deep copies
const original = {
name: "Ana",
address: { city: "Lisbon" },
joined: new Date(2024, 0, 15),
};
const shallow = { ...original };
shallow.name = "Bea";
shallow.address.city = "Porto";
console.log("Original city after shallow copy:", original.address.city);
const deep = structuredClone(original);
deep.address.city = "Faro";
console.log("Original city after deep copy:", original.address.city);
console.log("Date kept as a Date:", deep.joined instanceof Date);Line-by-line explanation
- Line 7Spread copies the top level only.
- Line 8Changing a top-level property on the copy is safe.
- Lines 9 to 10But
addressis shared, so the original's city changes too. - Lines 12 to 14
structuredClonecopies every level, so the original is untouched. - Line 15Dates stay real Date objects, unlike with JSON.
Common JavaScript Object Methods Mistakes
Calling a getter like a function
user.fullName(); // TypeError: not a functionuser.fullName;Thinking spread makes a deep copy
const copy = { ...order };
copy.items.push("extra"); // also changes order.itemsconst copy = structuredClone(order);
copy.items.push("extra");Mutating the target with Object.assign
const merged = Object.assign(defaults, options); // defaults is changedconst merged = Object.assign({}, defaults, options);
// or
const merged2 = { ...defaults, ...options };JavaScript Object Methods in Real Projects
Object helpers in real projects
Config systems merge defaults with user settings using spread. Forms turn Object.fromEntries(new FormData(form)) into a plain object ready to send. State libraries such as Redux require new objects instead of changes, and use Object.freeze in development to catch mistakes. Undo features save snapshots with structuredClone.
JavaScript Object Methods Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Get an array of the object's keys.
const car = { make: "Mini", year: 2020 };
console.log(Object.(car));
Object.keys returns the property names.What is logged?
const a = { list: [1] };
const b = { ...a };
b.list.push(2);
console.log(a.list.length, a === b);Which makes a deep copy of nested data?
What does a getter do?
withDiscount(product, percent) should return a new product with a lower price, leaving the original and its nested tags array unchanged. Also, product.label should be a getter. Fix it.
const product = {
name: "Lamp",
price: 40,
tags: ["home"],
label() { return `${this.name} £${this.price}`; },
};
function withDiscount(item, percent) {
const copy = item;
copy.price = item.price * (1 - percent / 100);
copy.tags.push("sale");
return copy;
}
const sale = withDiscount(product, 25);
console.log(sale.price, product.price, product.tags);- Discounted copy
- Original price unchanged
- Original tags unchanged
- label is a getter
JavaScript Object Methods Quiz
5 questions to check what you learned.
What does Object.values({ a: 1, b: 2 }) return?
- ["a", "b"]
- [1, 2]
- [["a", 1], ["b", 2]]
- 2
In { ...a, ...b }, which value wins if both have the same key?
- a's
- b's, because it comes later
- Neither, it's an error
- The larger one
Is Object.freeze deep?
- No, nested objects can still change
- Yes, every level
- Only for arrays
- Only in strict mode
What does JSON.parse(JSON.stringify(obj)) lose?
- Strings
- Numbers
- Nothing
- Functions, undefined values, and Date objects (which become strings)
What's the difference between Object.hasOwn(obj, "x") and "x" in obj?
- None
- hasOwn checks only the object itself; in also checks its prototype chain
- in only works on arrays
- hasOwn checks values
JavaScript Object Methods Challenge
A shopping basket object
Create an object basket with:
items: an array of{ name, price, qty }objects, starting empty- A method
add(name, price, qty = 1)that pushes an item - A getter
totalthat returns the sum ofprice * qtyfor all items - A getter
countthat returns the total quantity - A method
snapshot()that returns a deep copy ofitemsusingstructuredClone
- add and total
- count of all quantities
- total and count are getters
- snapshot is a deep copy