Chapter 4 · Real programs
Classes
Step 1 of 22 · Concept
A class writes the links for you
By the end of this lesson you can:
- write a class and make objects from it with
new, - keep a field private,
- extend a class and override its methods.
Under it all are lesson 16's links: a class builds them for you.
Step 2 of 22 · Concept
A class is a plan for linked objects
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
}
const a = new Account("Ada");
A class is a plan for objects. new Account("Ada") makes an empty object linked to Account.prototype, runs
the constructor with this set to it, and gives it back. The methods, here deposit, go on Account.prototype.
Step 3 of 22 · Trace
Watch new at work
new and constructor at work, one moment per frame. Press Next.
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
}
const a = new Account("Ada");
console.log(a);newmade an empty object linked toAccount.prototype.constructorruns withthispointing at it. Line 3 is next.- Line 3 gave the object its own
owner,"Ada". - Line 4 gave it
balance,0. The}endsconstructor. newgave the object back: the boxapoints at it. The frame is gone.- Line 11 printed the class's name, then the object's own properties. The program is finished.
Step 4 of 22 · Quiz
What does console.log show?
console.log shows an object's own properties (lesson 16), after its class's name. What does this program print?
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
}
const bo = new Account("Bo");
bo.deposit(5);
console.log(bo);
Step 5 of 22 · Demo
See it run
Run it and compare with your answer. Then log Object.getPrototypeOf(bo) === Account.prototype too.
Step 6 of 22 · Concept
The methods live on Account.prototype
| you write | gives |
|---|---|
Object.hasOwn(a, "deposit") | false |
Object.hasOwn(Account.prototype, "deposit") | true |
Object.hasOwn(a, "balance") | true |
There is one deposit for every account. a.deposit(5) finds it on the chain and runs it with this set to a.
Each object has its own owner and balance.
Step 7 of 22 · Quiz
Each object has its own balance
Type the missing line.
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
}
const a = new Account("Ada");
const b = new Account("Bo");
a.deposit(5);
b.deposit(2);
a.deposit(1);
console.log(a.balance);
console.log(b.balance);
Step 8 of 22 · Demo
See it run
Run it and compare with your answer: one method, two balances.
Step 9 of 22 · Exercise
Your turn: withdraw, but not too much
Add a method withdraw(amount) to Account. When amount is more than the balance, it logs Refused and changes
nothing; else it takes amount off. The program must print:
Refused
Balance: 3
balance alone is a variable, and there is none: ReferenceError: balance is not defined. The object's balance is
this.balance, as in deposit.
Write withdraw(amount) { ... } after deposit, inside the class. In it, an if on amount > this.balance, with an
else that takes the amount off.
if (amount > this.balance) {
Step 10 of 22 · Concept
A #field is private
class Account {
#balance = 0;
constructor(owner) {
this.owner = owner;
}
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
A private field starts with #. #balance = 0; at the top of the class gives each new object its own. Inside
the class it is this.#balance; outside, nothing can reach it:
console.log(a)hides it:Account { owner: 'Ada' }.a.#balanceoutside the class is aSyntaxError, found before any line runs.- A method such as
getBalance()gives it out.
Step 11 of 22 · Try it
Try it: read it the allowed way
Run it: SyntaxError: Private field '#balance' must be declared in an enclosing class, and nothing prints. Read the
balance with getBalance() instead.
On the last line, a.#balance becomes a.getBalance(): a method of the class may read the field.
Step 12 of 22 · Concept
Your toString shadows Object's
`${a}` turns a into text with toString. Found on Object.prototype, it gives [object Object] (lesson
16).
A toString() in the class sits lower on the chain, on Account.prototype, so it shadows that one:
toString() {
return `${this.owner}: ${this.balance}`;
}
console.log(a) does not use it: after a.deposit(5), it still shows Account { owner: 'Ada', balance: 5 }.
Step 13 of 22 · Demo
See it run
Run it: the template literal uses your toString, console.log(a) does not. Then remove toString and run it
again.
Step 14 of 22 · Demo
A method on its own loses this
const deposit = a.deposit; copies the reference to the function. Called as deposit(5), no object stands before the
dot, so this is undefined (lesson 16). Run it.
Step 15 of 22 · Demo
An arrow function keeps the this around it
In addAll, the arrow function has no this of its own: it uses addAll's, the basket. Run it. Then write
function (p) { ... } in place of the arrow function: that function gets its own this, undefined.
Step 16 of 22 · Concept
extends links two prototypes
class Savings extends Account {
constructor(owner, rate) {
super(owner);
this.rate = rate;
}
}
extends Account links Savings.prototype to Account.prototype. super(owner) runs Account's constructor,
which sets owner and balance on the new object.
Step 17 of 22 · Concept
Overriding is shadowing
class Savings extends Account {
constructor(owner, rate) {
super(owner);
this.rate = rate;
}
deposit(amount) {
super.deposit(amount + 1);
}
}
const s = new Savings("Bo", 2);
A deposit in Savings overrides Account's: it sits lower on the chain and shadows it (lesson 16). This one
adds a bonus of 1. super.deposit(...) runs Account's, with this still s.
instanceof walks the chain: s instanceof Savings and s instanceof Account are both true.
Step 18 of 22 · Quiz
Which deposit ran?
What does this program print?
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
}
class Savings extends Account {
constructor(owner, rate) {
super(owner);
this.rate = rate;
}
deposit(amount) {
super.deposit(amount + 1);
}
}
const s = new Savings("Bo", 2);
s.deposit(10);
console.log(s.balance);
Step 19 of 22 · Demo
See it run
Run it and compare with your answer. Then log s and s instanceof Account: Savings { owner: 'Bo', balance: 11,
rate: 2 } and true. Last, move this.rate = rate; above super(owner); and run it: the ReferenceError.
Step 20 of 22 · Check yourself
Check yourself: a gift card
Account is your class from Your turn: withdraw, but not too much.
- Write
class GiftCard extends Account. Itsconstructor(owner, amount)callssuper(owner), then setsthis.balance = amount. - Give it a
toString()for`${card}`:Ada's card: 20.00, withtoFixed(2). - Make Ada's card of 20,
withdraw(50), log`${card}`,withdraw(5), log it again.
The output must be:
Refused
Ada's card: 20.00
Ada's card: 15.00
In GiftCard's constructor, super(owner) comes first: this before it is the ReferenceError of the step
extends links two prototypes. withdraw needs nothing new: the card finds Account's on its chain.
Step 21 of 22 · Challenge · optional
Challenge: chain the calls
Optional.
Make a.deposit(5).withdraw(2).deposit(10) work on one line. The program must print:
13
Run it: TypeError: Cannot read properties of undefined (reading 'withdraw'). a.deposit(5) returns undefined, and
.withdraw(2) is read on what it returns.
Each method must give back the object it worked on, so the next call has an object before its dot. That object is
this.
return this;
Step 22 of 22 · Recap
Recap
classbuilds lesson 16's links:new Account(...)makes an object linked toAccount.prototype.- The methods live on
Account.prototype, one for every object; each object has its own properties. extendslinksSavings.prototypetoAccount.prototype;super(owner)comes beforethis.- Overriding is shadowing;
super.deposit(...)calls the next one up, andinstanceofwalks the chain. - The
Accountclass, withdepositand awithdrawthat refuses too much, comes back in lesson 18.
Next: lesson 18 makes withdraw refuse with an exception.
Review
3 questions from this lesson, in a new order each time. Answer from memory: 2 of 3 right on the first try passes. On a day after you finish the lesson, a pass earns 15 XP.
The review needs JavaScript.
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
}
const bo = new Account("Bo");
bo.deposit(5);
console.log(bo);
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
}
const a = new Account("Ada");
const b = new Account("Bo");
a.deposit(5);
b.deposit(2);
a.deposit(1);
console.log(a.balance);
console.log(b.balance);
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
// withdraw(amount): refuse more than the balance
}
const a = new Account("Ada");
a.deposit(5);
a.withdraw(10);
a.withdraw(2);
console.log(`Balance: ${a.balance}`);
class Account {
#balance = 0;
constructor(owner) {
this.owner = owner;
}
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
const a = new Account("Ada");
a.deposit(5);
console.log(a);
console.log(a.#balance);
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
toString() {
return `${this.owner}: ${this.balance}`;
}
}
const a = new Account("Ada");
a.deposit(5);
console.log(`${a}`);
console.log(a);
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
}
const a = new Account("Ada");
a.deposit(5);
const deposit = a.deposit;
deposit(5);
class Basket {
constructor() {
this.total = 0;
}
addAll(prices) {
prices.forEach((p) => {
this.total += p;
});
}
}
const b = new Basket();
b.addAll([2, 3, 5]);
console.log(b.total);
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
}
class Savings extends Account {
constructor(owner, rate) {
super(owner);
this.rate = rate;
}
deposit(amount) {
super.deposit(amount + 1);
}
}
const s = new Savings("Bo", 2);
s.deposit(10);
console.log(s.balance);
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
withdraw(amount) {
if (amount > this.balance) {
console.log("Refused");
} else {
this.balance -= amount;
}
}
}
// 1. class GiftCard extends Account
// 2. its toString(): "Ada's card: 20.00"
// 3. a card of 20: withdraw, log, withdraw, log
class Account {
constructor(owner) {
this.owner = owner;
this.balance = 0;
}
deposit(amount) {
this.balance += amount;
}
withdraw(amount) {
if (amount > this.balance) {
console.log("Refused");
} else {
this.balance -= amount;
}
}
}
const a = new Account("Ada");
a.deposit(5).withdraw(2).deposit(10);
console.log(a.balance);