Section 3 · Working with Data
Objects, Destructuring and Spread
18 min3 min readCode playground
Lesson at a glance
Model real records as objects, read and update their properties, understand that objects are shared by reference, and copy, update and unpack them with spread and destructuring.
- 1Objects: named values together
- 2References: two names, one object
- 3Spread: copy and update without mutating
- 4Destructuring: unpacking into variables
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
Until now an expense in Pocket was just a number. A real expense has several facts that belong together: what it was, how much, which category, when. Keeping those in separate arrays (labels[2], amounts[2], categories[2]) works until one array gets sorted and the others do not. You need one value that holds them all: an object.
Objects: named values together
An object is written with curly braces and contains properties, each a name and a value:
const expense = {
id: "exp-1",
label: "Coffee",
amount: 450,
category: "food",
};
console.log(expense.label); // dot notation
console.log(expense["amount"]); // bracket notation
console.log(expense.note); // missing property: undefinedDot notation is the everyday way to read a property. Brackets do the same job, but evaluate whatever is inside them, which you need when the property name is in a variable: const field = "category"; expense[field]. Reading a property that does not exist gives undefined, not an error.
You can add, change and remove properties after creation:
const expense = { label: "Coffee", amount: 450 };
expense.category = "food"; // add
expense.amount = 500; // change
delete expense.category; // remove
console.log(expense);
console.log(Object.keys(expense), Object.values(expense));When a variable already has the name you want for a property, you can write it once. { label, amount } is shorthand for { label: label, amount: amount }, and you will see it constantly in real code:
const label = "Bus";
const amount = 270;
const expense = { label, amount, category: "travel" };
console.log(expense);Object.keys and Object.values give you arrays of the names and the values, and Object.entries gives pairs, so for (const [key, value] of Object.entries(expense)) loops over every property.
Pocket’s data is now an array of objects, the most common shape of data in JavaScript, and everything from the last lesson still applies:
const expenses = [
{ id: "exp-1", label: "Coffee", amount: 450, category: "food" },
{ id: "exp-2", label: "Train", amount: 1220, category: "travel" },
{ id: "exp-3", label: "Lunch", amount: 1350, category: "food" },
];
const food = expenses.filter((e) => e.category === "food");
console.log(food.map((e) => e.label));References: two names, one object
Here is the most important thing to understand about objects and arrays. A variable does not hold the object; it holds a reference to it, like an address. Copying the variable copies the address:
const original = { label: "Coffee", amount: 450 };
const alias = original;
alias.amount = 999;
console.log(original.amount); // 999
console.log(alias === original); // true: the same object
console.log({ a: 1 } === { a: 1 }); // false: two different objects=== on objects compares references, not contents. Two objects that look identical are still two different objects.
This matters because functions receive references too. If a function changes an object it was given, the caller’s object changes:
function applyDiscount(expense) {
expense.amount = Math.round(expense.amount * 0.9);
return expense;
}
const lunch = { label: "Lunch", amount: 1350 };
const discounted = applyDiscount(lunch);
console.log(discounted.amount, lunch.amount); // both 1215The function looks as if it returns a discounted version, but it quietly rewrote the original as well. Somewhere else, a total that included lunch is now wrong, and nothing on screen says why. That is how bugs like “I only displayed the expenses, why did their amounts change?” happen. The fix is to build a new object instead of changing the one you were given, and spread makes that easy.
Spread: copy and update without mutating
Spread syntax, three dots, copies all the properties of an object (or items of an array) into a new one:
const expense = { id: "exp-1", label: "Coffee", amount: 450 };
const updated = { ...expense, amount: 500 };
console.log(expense.amount, updated.amount);
const list = [expense];
const longer = [...list, { id: "exp-2", label: "Bus", amount: 270 }];
console.log(list.length, longer.length);In { ...expense, amount: 500 }, properties are copied in order and the later amount overwrites the copied one. You get a new object, and the original is untouched. Combined with map, this is the standard way to change one item in a list without mutating anything:
const expenses = [
{ id: "exp-1", label: "Coffee", amount: 450 },
{ id: "exp-2", label: "Bus", amount: 270 },
];
const fixed = expenses.map((e) => (e.id === "exp-2" ? { ...e, amount: 300 } : e));
console.log(fixed[1].amount, expenses[1].amount);Destructuring: unpacking into variables
Destructuring pulls properties out of an object into variables of the same name:
const expense = { label: "Train", amount: 1220, category: "travel" };
const { label, amount, note = "no note" } = expense;
console.log(label, amount, note);
const [first, second] = ["food", "travel", "fun"];
console.log(first, second);The = "no note" is a default, used when the property is missing or undefined. For arrays, destructuring goes by position instead of name.
Destructuring is most useful in function parameters, where it documents exactly which properties a function needs:
function describe({ label, amount, category = "other" }) {
return `${label}: ${(amount / 100).toFixed(2)} (${category})`;
}
console.log(describe({ label: "Train", amount: 1220 }));Compare it with the version without destructuring, which repeats expense. on every line and hides which properties matter until you read the whole body. With destructuring, the first line of the function is its contract: give me something with a label, an amount, and optionally a category. Callers can pass a full expense object with ten other properties; the function simply ignores the rest.
The ... also works in reverse, as rest: const { id, ...details } = expense puts id in its own variable and every other property in a new object called details. It is a neat way to drop a property without mutating the original: everything you did not name ends up in the new object.
Pocket now has realistic data. Next you summarise it: totals per category, and lists sorted by amount.
Key takeaways
- An object groups named values (properties); read them with dot notation, or with brackets when the property name is in a variable.
- Objects and arrays are shared by reference, so two names can point at the same object and a change through one is visible through the other.
- Spread (
{ ...expense, amount: 500 },[...list, item]) makes a new, shallow copy, which is how you update data without mutating it. - Destructuring (
const { label, amount } = expense) pulls properties into variables, and works in function parameters with defaults.