You already know how to bundle related facts together as a
name, an age and an isPresent flag, all in a single value.
That's a big step — but a record only holds data. It just sits there. Real things don't
only have facts about them; they can do things too.
Think about a bank account. It has data — an owner's name and a balance — but it also has behaviour: you can pay money in, take money out, and ask how much is left. The data and the actions belong together: it makes no sense to "pay in" without an account to pay into. What we'd love is a way to package the data and the behaviour that acts on that data into one tidy unit. That unit is a class, and this is the heart of object-oriented programming (OOP).
The blueprint is written once; from it you can stamp out as many objects as you like, each with its own values. Let's build one.
Here is a whole BankAccount class. Read it slowly — every part has a name, and we'll
take them one at a time underneath.
Four ideas are doing all the work here:
owner, balance) — the typed data each object
stores. They're just like the fields of a record, but now they live inside the class.constructor that runs
once, at the moment an object is created, to set up its starting state. Its job is to
fill in the fields.deposit, withdraw, report) —
the behaviour: ordinary functions that live inside the class and work on its data.this — the word a method uses to mean "the very object I was
called on". this.balance is this account's balance, not any other's.new
A class on its own does nothing — it's only a design, like the plans for a house. To get something
you can actually use, you build an object from it with the keyword new:
Reading it left to right: new asks for a fresh object, BankAccount(...)
runs the constructor with the arguments "Aisha" and 100, and the finished
object is handed back and stored in acc. From then on you reach an object's fields and
call its methods with a dot — exactly like a record's fields —
acc.balance, acc.deposit(50).
The picture above is the whole idea in one image: one blueprint on the left, and the three separate objects it stamped out on the right. Each object has the same shape (an owner and a balance) but its own values.
The real power shows up the moment you make more than one object. Every call to
new produces a brand-new, independent account. Paying into one leaves the others
completely untouched, because each has its own balance field. Run this:
a and b are two different objects made from the same class. When we call
a.deposit(50), inside deposit the word this means a,
so only a.balance grows. Ben's account never hears about it. That separateness
is what makes objects so useful: you can model a whole bank — thousands of accounts — from a single
blueprint, and each keeps its own state.
Dog
The same recipe works for anything, not just money. Here a Dog class carries a name and
an age, and knows how to bark and to have a birthday. Notice how a method
can change a field (birthday nudges age up) and how it can
read other fields (describe uses both):
Same four ingredients, completely different subject. Once you can spot fields, constructor, methods and this, you can read almost any class you meet.
Inside a method, this always means "the object to the left of the dot on the call
that got me here". Write rex.bark() and, while bark runs,
this is rex; write bella.bark() and the very same code now
has this pointing at bella. One set of instructions, reused by every
object, each time bound to whichever object it was called on. That's why we don't have to write
bark separately for every dog — the method is shared, but this makes it
act on the right animal.
You met a TypeScript interface when learning about records: it describes the
shape of some data so the computer can check it. A class does that too — every object has
typed fields — but it goes further by attaching behaviour and a
constructor to that shape. Compare the two side by side:
With the record you must remember to call the right free function on the right data. With the class,
the deposit behaviour lives with the account, so you simply ask the object to
do it: acc2.deposit(50). Bundling data with the behaviour that belongs to it is the
idea OOP is built on.
The single most common muddle for beginners is confusing the class with an object. Hold this picture in your head:
Two things follow from this. First, you don't store data in the class — you store it in
the objects; BankAccount.balance is meaningless, acc.balance is real.
Second, every new makes a completely fresh, independent object:
So a class is a noun-you-design; an object is a noun-that-exists. "Dog" is a class; Rex and Bella are objects. Mixing them up leads to expecting one cookie to change when you reshape the cutter — it won't.