In the past few lessons, we went over all of the concepts you need to know about object-oriented programming.
We first started with objects and discussed the this
keyword, which is used to reference the current instance of the object.
1let user = {
2 firstName: "John",
3 lastName: "Doe",
4 printUserName: function () {
5 console.log(`${this.firstName} ${this.lastName}`);
6 },
7};
8
9user.printUserName();
1John Doe
We then explained that this
can be used outside of an object, which allows us to write constructor functions. Constructor functions are used to create objects programmatically.
1function User(name) {
2 this.name = name;
3 this.role = "Visitor";
4}
5
6let john = new User("John");
7
8console.log(john);
1User { name: 'John', role: 'Visitor' }
We also discussed the inheritance system for objects called prototypes.
1let user = {
2 name: "John Doe",
3
4 example() {
5 console.log(
6 "This method belongs to user, but can be inherited by the admin."
7 );
8 },
9};
10
11let admin = {
12 role: "Admin",
13};
14
15Object.setPrototypeOf(admin, user);
16
17admin.example();
1This method belongs to user, but can be inherited by the admin.
Next, we moved on to the class notation, which is a more standard way of writing constructor functions.
1class User {
2 constructor(firstName, lastName) {
3 this.firstName = firstName;
4 this.lastName = lastName;
5 }
6
7 printFullName() {
8 console.log(`${this.firstName} ${this.lastName}`);
9 }
10}
11
12let user = new User("John", "Doe");
13
14user.printFullName();
1John Doe
You can create an inheritance system for classes using the keyword extends
.
1class User {
2 constructor(name) {
3 this.name = name;
4
5 this.firstName;
6 this.lastName;
7 }
8}
9
10class Profile extends User {
11 create() {
12 console.log("Profile created.");
13 }
14}
15
16let profile = new Profile("John Doe");
17
18console.log(profile);
19console.log(Profile.prototype);
1Profile { firstName: 'John', lastName: 'Doe' }
2User {}
Properties and methods that are assigned to the class directly are called static properties and methods. These are often used as helper functions that operate on one or more classes. They don't exist in the object instance the class will create when you run new ClassName()
.
1class User {
2 constructor(name, date) {
3 this.name = name;
4 this.createdAt = date;
5 }
6
7 static createToday(name) {
8 return new this(name, new Date());
9 }
10}
11
12let user = User.createToday("John Doe");
13
14console.log(user);
1User { name: 'John Doe', createdAt: 2024-03-12T06:10:45.231Z }
Private properties and methods can only be accessed by other methods in the same class and are not visible to external programs. Currently, there isn't official language-level support for private properties/methods, so developers decided on a convention to mark them with underscores.
1class User {
2 constructor(name) {
3 this._name = name; // This property is private
4 this.status = "Admin"; // This property is public
5 }
6
7 _private() {
8 console.log("This method shouldn't be accessed from the outside.");
9 }
10}
However, official support might be coming very soon, which will allow you to mark them with #
.
1class User {
2 constructor() {}
3
4 #name; // This is a private property
5
6 get name() {
7 return this.#name; // The private property can only be accessed from the inside with "this".
8 }
9
10 #private() {
11 console.log("This method couldn't be accessed from the outside.");
12 }
13}
With all of these concepts in mind, you are ready to create your first JavaScript application.
Building a banking app
In this lesson, we are going to build a banking app, where the user can open one or more savings accounts, deposit money in them, or withdraw money from them. When the money sits inside the savings account, the customer will be able to receive interest.
Inside this app, there should be a base class that offers the most basic functionalities of a bank account, such as depositing and withdrawing money and, of course, getting the account balance and number.
On top of this base class, there should be a SavingsAccount
, because a savings account is just a regular bank account, with the ability to receive interest. There should be an extra method to achieve this functionality.
There should also be a Customer
class. The customer could have multiple accounts, the ability to open a new account, and the ability to view the balance in all accounts.
The base class
Let's start with the base class.
1class BankAccount {
2 constructor(accountNumber, balance = 0) {
3 this._accountNumber = accountNumber;
4 this._balance = balance;
5 }
6}
A bank account should have an account number and a balance. For security reasons, both of them should be private. These values should only be accessed through a getter, and the customer's identity should be verified.
Of course, we won't be able to do that right now, so let's use a comment as a replacement.
1class BankAccount {
2 constructor(accountNumber, balance = 0) {
3 this._accountNumber = accountNumber;
4 this._balance = balance;
5 }
6
7 get balance() {
8 // There should be a verification mechanism here
9 //
10 // if (verified) {
11 // return this._balance;
12 // } else {
13 // console.log("You don't have access to this information.");
14 // }
15
16 return this._balance;
17 }
18
19 get accountNumber() {
20 // There should be a verification mechanism here
21 return this._accountNumber;
22 }
23}
We also need methods for depositing and withdrawing.
1class BankAccount {
2 constructor(accountNumber, balance = 0) {
3 this._accountNumber = accountNumber;
4 this._balance = balance;
5 }
6
7 get balance() {
8 return this._balance;
9 }
10
11 get accountNumber() {
12 return this._accountNumber;
13 }
14
15 deposit(amount) {
16 // This is a shortcut for this._balance = this._balance + amount
17 this._balance += amount;
18 }
19
20 withdraw(amount) {
21 if (this._balance >= amount) {
22 // If balance is greater than the withdraw amount
23 this._balance -= amount; // This is a shortcut for this._balance = this._balance - amount
24 } else {
25 // If not, print "Insufficient funds"
26 console.log("Insufficient funds");
27 }
28 }
29}
Now, let's test if this code works:
1let account = new BankAccount("1234567890");
2console.log(account);
3
4account.deposit(1000); // Deposit $1000
5console.log(account);
6
7account.deposit(1500); // Deposit another $1500
8console.log(account);
9
10account.withdraw(2000); // Withdraw $2000
11console.log(account);
12
13account.withdraw(1500); // Withdraw another $1500
1BankAccount { _accountNumber: '1234567890', _balance: 0 }
2BankAccount { _accountNumber: '1234567890', _balance: 1000 }
3BankAccount { _accountNumber: '1234567890', _balance: 2500 }
4BankAccount { _accountNumber: '1234567890', _balance: 500 }
5Insufficient funds
The SavingsAccount class
Next, you'll need a SavingsAccount
, which extends BankAccount
but offers one extra method to calculate and deposit interest to the account.
1class SavingsAccount extends BankAccount {
2 static INTEREST_RATE = 0.05; // 5% interest rate
3
4 addInterest() {
5 const interest = this.balance * SavingsAccount.INTEREST_RATE;
6 this.deposit(interest); // Use the same deposit() method from BankAccount
7 }
8}
1let savingsAccount = new SavingsAccount("1234567890");
2console.log(savingsAccount);
3
4savingsAccount.deposit(1000); // Deposit $1000
5console.log(savingsAccount);
6
7// setInterval() runs the function repeatedly after the specified time interval
8setInterval(() => {
9 savingsAccount.addInterest();
10 console.log(savingsAccount);
11}, 1000);
In this case, we are using the built-in function setInterval()
to run the addInterest()
method. It works similarly to the setTimeOut()
function we've seen before, except setInterval()
will run the function repeatedly. In this case, the passed function will be executed every second (1000ms).
Now run this demo code and watch the magical savings account grow your money every second.
1SavingsAccount { _accountNumber: '1234567890', _balance: 0 }
2SavingsAccount { _accountNumber: '1234567890', _balance: 1000 }
3SavingsAccount { _accountNumber: '1234567890', _balance: 1050 }
4SavingsAccount { _accountNumber: '1234567890', _balance: 1102.5 }
5SavingsAccount { _accountNumber: '1234567890', _balance: 1157.625 }
6SavingsAccount { _accountNumber: '1234567890', _balance: 1215.50625 }
The Customer class
1class Customer {
2 constructor(name, accounts = []) {
3 this._name = name;
4 this._accounts = accounts;
5 }
6
7 openAccount(account) {
8 this._accounts.push(account);
9 }
10
11 get totalBalance() {
12 let totalBalance = 0;
13 for (const account of this._accounts) {
14 totalBalance += account.balance;
15 }
16 return totalBalance;
17 }
18}
The Customer has two properties, name
and accounts
. The accounts
is an array because each customer can have multiple accounts. The openAccount()
method pushes a new account element to the accounts
array.
You also need a totalBalance
getter that combines the balance in all accounts, giving the customer an overview of their finances.
1// Create new customer
2let customer = new Customer("John Doe");
3
4// Create new accounts
5let sa1 = new SavingsAccount("SA001", 1000); // Create a new account SA001 and deposit $1000
6let sa2 = new SavingsAccount("SA002", 1500); // Create a new account SA002 and deposit $1500
7let sa3 = new SavingsAccount("SA003", 2000); // Create a new account SA003 and deposit $2000
8
9// Tie the accounts to the user
10customer.openAccount(sa1);
11customer.openAccount(sa2);
12customer.openAccount(sa3);
13
14// Get total balance
15console.log(customer.totalBalance);
16
17// Adding interests
18setInterval(() => {
19 sa1.addInterest();
20 sa2.addInterest();
21 sa3.addInterest();
22
23 console.log(customer.totalBalance);
24}, 1000);
14500
24725
34961.25
45209.3125
55469.778125
65743.2670312499995