Objects, properties and methods
So far your data has been loose variables: a customer here, a plate count there. Real data arrives in clumps — one order has a customer, a count, an address and a status, and those belong together. An object is that clump.
Making one
const order = {
customer: 'Priya',
plates: 3,
paid: true,
};
console.log(order.customer);
console.log(order.plates * 80);
Priya
240
Keys on the left, values on the right, commas between. A value can be anything — a number, a string, an array, another object, a function.
Two ways to reach a property
console.log(order.customer);
console.log(order['customer']);
Dot notation is what you will write. Brackets are for two cases dot cannot handle.
Keys that are not valid names:
const order = { 'delivery note': 'ring twice' };
console.log(order['delivery note']);
ring twice
Keys you do not know until the code runs:
const key = 'plates';
console.log(order[key]);
3
That distinction matters. order[key] looks up whatever key contains;
order.key looks for a property literally named key. Getting undefined from
a lookup that should have worked is very often this.
Missing properties give undefined
console.log(order.discount);
undefined
Not an error. This is the friendly part — until you go one level further:
console.log(order.address.city);
TypeError: Cannot read properties of undefined (reading 'city')
order.address is undefined, and undefined has no city. This is the
single most common runtime error in JavaScript, and it has a dedicated fix
which is two lessons away.
To ask whether a property exists:
console.log('customer' in order);
console.log(order.customer !== undefined);
in is the honest answer, because a property can exist and hold undefined.
Changing things
const order = { customer: 'Priya', plates: 3 };
order.plates = 5;
order.status = 'delivered';
delete order.plates;
console.log(order);
{ customer: 'Priya', status: 'delivered' }
All of that on a const, because — module 2 — const protects the binding, not
the contents.
Methods
A function stored on an object is a method:
const order = {
plates: 3,
rate: 80,
total() {
return this.plates * this.rate;
},
};
console.log(order.total());
240
this is the object, because of the dot. Module 3 covered exactly why, and why
this must not be an arrow function.
Shorthand
When a variable and the key share a name:
const customer = 'Priya';
const plates = 3;
const order = { customer, plates };
console.log(order);
{ customer: 'Priya', plates: 3 }
You will see this constantly. Computed keys use brackets:
const field = 'status';
const update = { [field]: 'delivered' };
console.log(update);
{ status: 'delivered' }
Without the brackets the key would literally be field.
Walking an object
Three functions turn an object into an array so you can loop it:
const order = { customer: 'Priya', plates: 3, paid: true };
console.log(Object.keys(order));
console.log(Object.values(order));
console.log(Object.entries(order));
[ 'customer', 'plates', 'paid' ]
[ 'Priya', 3, true ]
[ [ 'customer', 'Priya' ], [ 'plates', 3 ], [ 'paid', true ] ]
Object.entries with destructuring is the readable way to loop:
for (const [key, value] of Object.entries(order)) {
console.log(`${key}: ${value}`);
}
customer: Priya
plates: 3
paid: true
Destructuring is the fourth lesson of this module; for now it is the same square
brackets you saw with .entries() on an array.
These replace for...in from module 2, and they are better: they only give
you the object's own properties, with no inherited surprises.
Keys are always strings
const weird = {};
weird[1] = 'number one';
weird['1'] = 'string one';
console.log(weird);
{ '1': 'string one' }
One property, not two. Object keys are converted to strings, so 1 and
'1' are the same key and the second write overwrote the first. If you need
real number keys — or object keys — you need a Map, which is lesson six.
The trap: objects are handled by reference
This is the most important thing in the lesson.
const a = { plates: 3 };
const b = a;
b.plates = 9;
console.log(a.plates);
9
b = a did not copy the object. It copied a reference to it — both names point
at one object, and changing it through either name changes the same thing.
This also explains why two identical-looking objects are never equal:
console.log({} === {});
false
=== on objects asks "are these the same object", not "do they look alike".
Two separate empty objects are two objects.
To make an actual copy, spread it:
const original = { plates: 3 };
const copy = { ...original };
copy.plates = 9;
console.log(original.plates);
3
But that copy is shallow — one level deep only:
const base = { customer: 'Priya', address: { city: 'Pune' } };
const clone = { ...base };
clone.address.city = 'Mumbai';
console.log(base.address.city);
Mumbai
The outer object was copied; address was not — both objects point at the same
inner one. This bug is hard to find because the copy looked like it worked.
For a genuinely deep copy:
const fresh = { customer: 'Priya', address: { city: 'Pune' } };
const deep = structuredClone(fresh);
deep.address.city = 'Nagpur';
console.log(fresh.address.city);
Pune
structuredClone is built into every current browser and Node. It cannot copy
functions, and it will refuse if the object contains any.
Check your work
order['delivery note'] works and order.delivery note is a syntax error.
Brackets handle keys that are not valid identifiers, and keys held in variables.
order[key] where key is 'plates' gives 3; order.key gives
undefined, because the second looks for a property actually named key.
A missing property is undefined, not an error. Going one level deeper is an
error: TypeError: Cannot read properties of undefined (reading 'city').
You can add, change and delete properties on a const object. const stops
the name being repointed, nothing more.
weird[1] and weird['1'] are the same property. Object keys are strings,
so the object ends up { '1': 'string one' } with one key.
b = a then b.plates = 9 changes a too. Objects are held by reference.
{} === {} is false. Equality on objects asks whether they are the same
object.
{ ...original } copies one level. A nested object is shared, so
clone.address.city = 'Mumbai' changes base as well. structuredClone copies
all the way down.
Object.keys, .values and .entries give ['customer', 'plates', 'paid'],
['Priya', 3, true] and the pairs.
updateOrder needs to know about the nested level:
function updateOrder(order, changes) {
const updated = { ...order, ...changes };
if (changes.address) {
updated.address = { ...order.address, ...changes.address };
}
return updated;
}
A single spread handles { plates: 5 } correctly. It does not handle
{ address: { city: 'Mumbai' } } — that would replace the whole address,
losing the pincode, and any address the copy keeps would still be shared with the
original. The second spread rebuilds the address from the old one plus the
changes. This is why deeply nested state is unpleasant to update by hand, and why
libraries exist for it.
Practice
- Build an order object with a customer, a plate count, a rate and a nested address containing a city and a pincode. Print the city.
- Read a property with dot notation and the same one with brackets.
- Put a key in a variable and look it up with
order[key]. Then do it wrong withorder.keyand explain theundefined. - Add a
'delivery note'key with a space in it and read it back. - Cause the deep-read error. Read
order.discount(fine) and thenorder.discount.amount(not fine). Read the message and identify which part wasundefined. - Add a
total()method and call it. Then rewrite it as an arrow and watch it break — this is module 3's rule, in the place it bites. - Loop the object with
Object.entriesand print each key and value. - Set both
obj[1]andobj['1']and confirm you end up with one property. - Prove reference semantics. Assign an object to a second variable, change it through the second, and read it through the first. Then do the same with a number and note the difference.
- Harder. Write
updateOrder(order, changes)returning a new order with the changes applied and the original untouched. Test it with a change to a top-level field, then with a change to something insideaddress, and work out why the second one needs more than a spread.
Next: arrays — the other way data clumps, and the methods you will use daily.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship