In this lab, we’ll build on our knowledge of basic JavaScript classes and introduce inheritance, a key concept in object-oriented programming. Inheritance allows a class (subclass) to inherit properties and methods from another class (base class), promoting code reuse and making it easier to create complex relationships between objects.
This lab expands upon the previous JavaScript classes with the following:
- Person and Employee Classes: The
Employee
class will inherit from thePerson
class, allowing employees to have commonPerson
attributes, as well as additional job-related properties and methods. - Vehicle and Car Classes: The
Car
class will inherit fromVehicle
, sharing the basic vehicle properties and behaviors while adding specialized methods specific to cars.
By the end of this lab, you will:
- Understand how inheritance works in JavaScript and how it can streamline code organization.
- Learn how to define subclasses that extend the functionality of base classes.
- Be able to create objects from subclasses and demonstrate how they inherit and extend their base class methods and properties.
This advanced lab will reinforce foundational concepts while teaching you how to model more intricate relationships between objects, preparing you for further development in object-oriented JavaScript programming.
Your project should be structured as follows:
javascript-classes-inheritance/
├── index.js
└── README.md
-
Create a project folder named javascript-classes-inheritance to store your project files.
-
Inside the javascript-classes-inheritance folder, create a file named index.js. This will be your main JavaScript file where all your code will be written.
In this step, you will create a Person
class to model a person with specific attributes and behaviors. The Person
class will have properties to store a person’s name
and age
, as well as methods to introduce the person and celebrate their birthday. Later, you’ll call this class to create and log different Person
objects with unique names and ages, demonstrating the reusability of the class to represent various individuals.
- Add the following code to your
index.js
file:
// Person class definition
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
haveBirthday() {
this.age += 1;
console.log(`It's my birthday! I am now ${this.age} years old.`);
}
}
Explanation:
-
Creating the Class Structure: The
Person
class acts as a template for creating objects that share common properties (name
andage
) and methods (greet
andhaveBirthday
). This structure demonstrates object-oriented programming (OOP) in JavaScript, where classes are used to define and organize code based on real-world objects. -
Constructor Function: The
constructor(name, age)
is a special function that automatically runs when a newPerson
instance is created. It initializes eachPerson
object with specific values forname
andage
based on the arguments provided when creating a new instance. -
greet
Method: Thegreet
method logs a personalized greeting that includes thename
andage
properties. By using template literals (${}
syntax), the greeting dynamically incorporates each instance's unique information, making thePerson
class reusable for various individuals. -
haveBirthday
Method: ThehaveBirthday
method increases theage
by one each time it’s called, simulating a birthday celebration. It also logs a celebratory message with the updated age, showcasing how the class can change an object’s properties over time. -
Using the Class Later: After defining the class, we’ll create different
Person
instances and log unique values to the console. Each instance will have its ownname
andage
, illustrating how this class can represent multiple people with distinct attributes and behaviors.
In this step, you will create an Employee
subclass that inherits from the Person
class. This subclass will add job-related properties and methods specific to employees, while also inheriting the properties and methods defined in Person
. Using inheritance in this way makes it easy to extend existing classes with additional functionality, keeping your code organized and reusable.
- Add the following code to your
index.js
file:
// Subclass Employee, inheriting from Person
class Employee extends Person {
constructor(name, age, jobTitle, salary) {
super(name, age); // Call the parent class (Person) constructor
this.jobTitle = jobTitle;
this.salary = salary;
}
work() {
console.log(`${this.name} is working as a ${this.jobTitle}.`);
}
// Method override to include job title in the greeting
greet() {
console.log(`Hello, my name is ${this.name}, I am ${this.age} years old, and I work as a ${this.jobTitle}.`);
}
showSalary() {
console.log(`My salary is $${this.salary} per year.`);
}
}
Explanation:
-
Inheritance with
extends
: TheEmployee
class uses theextends
keyword to inherit fromPerson
. This means thatEmployee
instances automatically have access to all properties (name
andage
) and methods (greet
andhaveBirthday
) defined in thePerson
class, enabling reuse of the base functionality. -
Constructor with
super()
: Theconstructor(name, age, jobTitle, salary)
function inEmployee
callssuper(name, age)
, which refers to the constructor ofPerson
. This initializes thename
andage
properties for theEmployee
instance by leveragingPerson
's constructor, avoiding redundancy. -
Additional Properties (
jobTitle
andsalary
):Employee
introduces additional properties,jobTitle
andsalary
, that are specific to employee objects. These properties do not exist in thePerson
class, highlighting how inheritance allows for customization in subclasses while maintaining shared functionality from the base class. -
Method Override (
greet
): Thegreet
method inEmployee
overrides thegreet
method inherited fromPerson
. By redefininggreet
,Employee
provides a customized message that includes the employee’sjobTitle
. This allows each employee instance to display job-specific information in their greeting, without changing the originalPerson
class behavior. -
New Methods (
work
andshowSalary
): TheEmployee
class introduces two additional methods:work
logs a message describing the employee’s job role.showSalary
displays the employee's salary.
These methods are unique to
Employee
, demonstrating how subclasses can have specialized methods that don’t exist in the base class, enabling specific behaviors for different types of objects.
In this step, you will create a Vehicle
class that serves as a blueprint for different types of vehicles. This base class will have properties to store the vehicle’s make
, model
, year
, and running state (isRunning
). Additionally, it will have methods to start, stop, and calculate the vehicle's age. In later steps, you'll extend this class to create specific types of vehicles with additional functionality.
- Add the following code to your
index.js
file:
// Base Vehicle class definition
class Vehicle {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.isRunning = false;
}
start() {
this.isRunning = true;
console.log(`${this.make} ${this.model} has started.`);
}
stop() {
this.isRunning = false;
console.log(`${this.make} ${this.model} has stopped.`);
}
getAge(currentYear) {
return currentYear - this.year;
}
}
Explanation:
-
Creating the Vehicle Class Structure: The
Vehicle
class represents general vehicle attributes likemake
,model
,year
, and a property,isRunning
, that tracks whether the vehicle is currently on or off. This class provides a foundation for creating vehicle objects with shared characteristics. -
Constructor Function: The
constructor(make, model, year)
initializes eachVehicle
instance with specific values formake
,model
, andyear
, and setsisRunning
tofalse
by default to represent that the vehicle is initially off. -
start
Method: Thestart
method changesisRunning
totrue
and logs a message indicating that the vehicle has started. This simulates the action of turning the vehicle on. -
stop
Method: Thestop
method setsisRunning
back tofalse
and logs a message indicating that the vehicle has stopped, simulating the action of turning off the vehicle. -
getAge
Method: ThegetAge
method takescurrentYear
as a parameter and calculates the vehicle’s age by subtracting itsyear
from thecurrentYear
. This method allows each vehicle instance to report its age based on the current year, demonstrating how the class can provide useful, calculated information about each instance. -
Using the Class Later: After defining the
Vehicle
class, we can create various vehicle objects with unique values formake
,model
, andyear
. This base class will also serve as a foundation for subclasses, which can inherit and expand upon its functionality in upcoming steps.
In this step, you will create a Car
subclass that inherits from the Vehicle
class. This subclass will add properties and methods specific to cars, while inheriting the general attributes and behaviors from Vehicle
. Using inheritance here allows the Car
class to reuse the Vehicle
class’s methods, such as start
and stop
, while adding unique functionality.
- Add the following code to your
index.js
file:
// Subclass Car, inheriting from Vehicle
class Car extends Vehicle {
constructor(make, model, year, color) {
super(make, model, year); // Call the parent class (Vehicle) constructor
this.color = color;
}
paint(newColor) {
console.log(`${this.make} ${this.model} is now painted ${newColor}.`);
this.color = newColor;
}
// Additional method specific to Car
honk() {
console.log(`${this.make} ${this.model} goes 'beep beep!'`);
}
}
Explanation:
-
Inheritance with
extends
: TheCar
class uses theextends
keyword to inherit fromVehicle
. This allowsCar
instances to have access to all properties (make
,model
,year
,isRunning
) and methods (start
,stop
,getAge
) defined inVehicle
. By inheriting fromVehicle
, theCar
class can reuse these common vehicle behaviors without needing to redefine them, making the code more modular and efficient. -
Constructor with
super()
: Theconstructor(make, model, year, color)
inCar
callssuper(make, model, year)
, which references theVehicle
constructor. This call initializesmake
,model
, andyear
properties by usingVehicle
's setup. The additionalcolor
property is unique toCar
and is initialized directly within theCar
constructor. -
Additional Property (
color
): Thecolor
property is specific to theCar
class and represents the car's color. This property is not part of theVehicle
class, illustrating how subclasses can expand upon their base class with more specialized properties. -
New Methods (
paint
andhonk
):paint
Method: Thepaint
method changes thecolor
property to a new value (newColor
) and logs a message indicating the updated color. This feature allows eachCar
instance to be repainted dynamically, simulating a real-world behavior for car objects.honk
Method: Thehonk
method logs a sound message to simulate a car horn. This behavior is specific to theCar
class, demonstrating how subclasses can introduce new methods that aren’t part of the base class. This adds unique functionality toCar
without affectingVehicle
.
-
Using the Class Later: After defining the
Car
class, you can create car objects that inherit basic vehicle properties and methods fromVehicle
, while also being able to perform car-specific actions like repainting and honking. This setup demonstrates the power of inheritance by allowing both shared and specialized behaviors in different types of objects.
In this step, we’ll begin testing the Employee
subclass to ensure that it correctly inherits properties and methods from the Person
class, while also demonstrating its unique methods and attributes. By creating instances of Employee
, we can confirm that the inherited methods and additional functionality work as expected.
- Add the following code to your
index.js
file to test theEmployee
subclass:
// Testing the Employee subclass
const alice = new Employee("Alice", 30, "Software Engineer", 90000);
alice.greet(); // Output: Hello, my name is Alice, I am 30 years old, and I work as a Software Engineer.
alice.haveBirthday(); // Output: It's my birthday! I am now 31 years old.
alice.work(); // Output: Alice is working as a Software Engineer.
alice.showSalary(); // Output: My salary is $90000 per year.
-
Run your code using Node.js in the terminal to test the
Vehicle
instance:node index.js
Hello, my name is Alice, I am 30 years old, and I work as a Software Engineer.
It's my birthday! I am now 31 years old.
Alice is working as a Software Engineer.
My salary is $90000 per year.
Explanation
In this step, we tested the Employee
subclass to verify that it correctly inherited properties and methods from the Person
class, and that its unique properties and methods were functioning as expected. Here’s a breakdown of what we did:
-
Created an
Employee
Instance:- We created an instance of the
Employee
class namedalice
using theEmployee
constructor, passing values forname
,age
,jobTitle
, andsalary
. - This instance initialization used both the properties inherited from
Person
(name
andage
) and new properties specific toEmployee
(jobTitle
andsalary
).
- We created an instance of the
-
Tested the
greet
Method (Overridden):- We called the
greet
method onalice
. - This method was overridden in the
Employee
class to include thejobTitle
in the greeting message. - The output confirmed that
alice.greet()
correctly included her name, age, and job title.
- We called the
-
Tested the
haveBirthday
Method (Inherited):- We called
alice.haveBirthday()
, which increasedalice
'sage
by one and logged a birthday message. - This method was inherited directly from
Person
, demonstrating thatEmployee
instances can use methods defined in the parent class. - The output showed that the method worked correctly, updating the age and logging the new age.
- We called
-
Tested the
work
Method (Unique toEmployee
):- We called the
work
method onalice
. - This method is unique to
Employee
and logs a message that describesalice
's job role. - The output verified that
alice.work()
correctly displayed her current job activity.
- We called the
-
Tested the
showSalary
Method (Unique toEmployee
):- We called
alice.showSalary()
to logalice
's salary. - This method is also unique to
Employee
, displaying thesalary
property specific to employee instances. - The output confirmed that
alice.showSalary()
worked as intended, showing the correct salary amount.
- We called
-
Ran the Code to Confirm Expected Output:
- After adding the test code to
index.js
, we ran the file usingnode index.js
in the terminal. - The expected output in the console matched the designed behavior of each method, verifying that both inherited and subclass-specific functionalities are working as intended.
- After adding the test code to
This testing confirmed the functionality of our class inheritance, demonstrating how Employee
instances inherit methods from Person
and can introduce specialized methods and properties that are specific to employees.
In this step, we’ll test the Employee
subclass further by creating an additional Employee
instance. This will help confirm that each Employee
object can independently represent unique individuals with their own properties, and that both inherited and unique methods function correctly.
- Add the following code to your
index.js
file to test the newEmployee
instance:
// Testing another Employee instance
const bob = new Employee("Bob", 25, "Developer", 98000);
bob.greet(); // Output: Hello, my name is Bob, I am 25 years old, and I work as a Developer.
bob.work(); // Output: Bob is working as a Developer.
bob.showSalary(); // Output: My salary is $98000 per year.
-
Run your code using Node.js in the terminal to test the
Vehicle
instance:node index.js
Hello, my name is Bob, I am 25 years old, and I work as a Developer.
Bob is working as a Developer.
My salary is $98000 per year.
Explanation
In this step, we tested a second Employee
instance, bob
, to confirm that each Employee
object can hold unique properties and utilize both inherited and subclass-specific methods independently. Here’s what was verified:
-
Created a New Employee Instance:
- We created a new instance of
Employee
namedbob
with values forname
,age
,jobTitle
, andsalary
that are unique to this instance. - This instance was initialized using properties inherited from
Person
(name
andage
) as well as properties specific toEmployee
(jobTitle
andsalary
), confirming that the subclass can accept and use its own unique data.
- We created a new instance of
-
Tested the
greet
Method (Overridden):- We called
bob.greet()
, which is overridden inEmployee
to include thejobTitle
. - This confirmed that the method correctly logs Bob’s name, age, and job title, verifying that the overridden
greet
method functions as expected for individual instances.
- We called
-
Tested the
work
Method (Unique toEmployee
):- We called
bob.work()
, a method unique to theEmployee
subclass. - The output verified that
bob.work()
correctly logs Bob’s job activity as a Developer, showcasing howEmployee
can introduce behavior not present inPerson
.
- We called
-
Tested the
showSalary
Method (Unique toEmployee
):- We called
bob.showSalary()
to display Bob’s salary. - This method, specific to
Employee
, confirmed that thesalary
property functions independently in each instance. - The output confirmed that
bob.showSalary()
displayed the correct salary set for Bob.
- We called
-
Ran the Code to Confirm Expected Output:
- After adding the new test code, we ran
node index.js
in the terminal. - The console output matched the expected behavior of each method, validating that each
Employee
instance can hold unique properties and operate independently.
- After adding the new test code, we ran
In this step, we’ll test the Car
subclass to confirm that it correctly inherits properties and methods from the Vehicle
class while demonstrating its unique methods and attributes. By creating an instance of Car
, we can verify that both the inherited and subclass-specific methods work as expected.
- Add the following code to your
index.js
file to test theCar
subclass:
// Testing the Car subclass
const car = new Car("Toyota", "Corolla", 2015, "red");
car.start(); // Output: Toyota Corolla has started.
console.log(`Is the car running? ${car.isRunning}`); // Output: Is the car running? true
car.honk(); // Output: Toyota Corolla goes 'beep beep!'
car.paint("blue"); // Output: Toyota Corolla is now painted blue.
car.stop(); // Output: Toyota Corolla has stopped.
-
Run your code using Node.js in the terminal to test the
Vehicle
instance:node index.js
Toyota Corolla has started.
Is the car running? true
Toyota Corolla goes 'beep beep!'
Toyota Corolla is now painted blue.
Toyota Corolla has stopped.
Explanation
In this step, we tested the Car
subclass to ensure it correctly inherits from the Vehicle
class and performs both inherited and subclass-specific operations. Here’s a breakdown of what we confirmed:
-
Created a Car Instance:
- We created a new instance of
Car
namedcar
with specific values formake
,model
,year
, andcolor
. - This initialization used inherited properties from
Vehicle
(make
,model
,year
,isRunning
) along with thecolor
property unique toCar
.
- We created a new instance of
-
Tested the
start
Method (Inherited):- We called
car.start()
to setisRunning
totrue
and log a message that the car has started. - This method, inherited from
Vehicle
, verified thatCar
can use starting functionality defined in its parent class.
- We called
-
Checked
isRunning
Property (Inherited):- We logged the
isRunning
property to check the car’s running state after callingstart
. - This showed that
Car
correctly inherited theisRunning
property fromVehicle
and maintained its state.
- We logged the
-
Tested the
honk
Method (Unique to Car):- We called
car.honk()
, which logs a sound message to simulate the car horn. - This method is unique to
Car
, demonstrating how subclasses can add custom methods beyond those in the base class.
- We called
-
Tested the
paint
Method (Unique to Car):- We called
car.paint("blue")
to change thecolor
property and log a message reflecting the new color. - This confirmed that
Car
instances can dynamically update theircolor
, showcasing the subclass-specific functionality.
- We called
-
Tested the
stop
Method (Inherited):- We called
car.stop()
to setisRunning
tofalse
and log a message indicating that the car has stopped. - This method, inherited from
Vehicle
, confirmed thatCar
retains the ability to stop, as provided by the base class.
- We called
-
Ran the Code to Confirm Expected Output:
- After adding the test code, we ran
node index.js
in the terminal. - The console output matched the expected results for each method, verifying that
Car
can use both inherited methods fromVehicle
and its own unique methods independently.
- After adding the test code, we ran
In this step, we’ll test the Vehicle
class directly by creating an instance of Vehicle
called truck
. This will confirm that Vehicle
instances can be created independently and that they function correctly with their own methods. Since Vehicle
is the base class, we won’t have any subclass-specific methods, allowing us to focus solely on the inherited functionality.
- Add the following code to your
index.js
file to test theVehicle
class:
// Testing the Vehicle class
const truck = new Vehicle("Ford", "F-150", 2018);
truck.start(); // Output: Ford F-150 has started.
truck.stop(); // Output: Ford F-150 has stopped.
-
Run your code using Node.js in the terminal to test the
Vehicle
instance:node index.js
Ford F-150 has started.
Ford F-150 has stopped.
Explanation
In this step, we created and tested an instance of the Vehicle
class, named truck
, to confirm that the class functions correctly with its own properties and methods, independent of any subclasses. Here’s a breakdown of the verification:
-
Created a Vehicle Instance:
- We instantiated a
Vehicle
object calledtruck
with the propertiesmake
,model
, andyear
. - This initialization confirmed that
Vehicle
can represent general vehicle data without needing subclass-specific properties or methods.
- We instantiated a
-
Tested the
start
Method:- Calling
truck.start()
set theisRunning
property totrue
and logged a message stating that the vehicle has started. - This output verified that the
start
method functions correctly for instances ofVehicle
, showing thattruck
can be set to a running state.
- Calling
-
Tested the
stop
Method:- We then called
truck.stop()
, which setisRunning
tofalse
and logged a message indicating that the vehicle has stopped. - This confirmed that the
stop
method works as expected, toggling theisRunning
state back tofalse
and logging the appropriate message for a non-running state.
- We then called
-
Ran the Code to Confirm Expected Output:
- After adding this test code, we ran the file with
node index.js
in the terminal. - The console output matched the expected results for both the
start
andstop
methods, verifying thatVehicle
instances function as designed, providing foundational functionality that can be shared by subclasses likeCar
.
- After adding this test code, we ran the file with
This test validated that Vehicle
operates correctly as a standalone class, supporting core behaviors essential for managing the state of any vehicle object.
In this final step, we’ll test the getAge
method for both Car
and Vehicle
instances. This method calculates the age of the vehicle based on the current year, verifying that each instance can use inherited methods to perform calculations specific to its properties.
- Add the following code to your
index.js
file to test thegetAge
method for bothcar
andtruck
:
// Final testing of the getAge method for both car and truck
const currentYear = new Date().getFullYear();
console.log(`The car is ${car.getAge(currentYear)} years old.`); // Output depends on current year
console.log(`The truck is ${truck.getAge(currentYear)} years old.`); // Output depends on current year
-
Run your code using Node.js in the terminal to test the
Vehicle
instance:node index.js
The output will vary depending on the current year, as it dynamically calculates the age of each vehicle based on the year property:
The car is X years old.
The truck is Y years old.
Explanation of Step 10: Final Testing of the getAge
Method
In this step, we tested the getAge
method for both Car
and Vehicle
instances to confirm that it accurately calculates the age of each vehicle based on the current year. Here’s what we verified:
-
Calculated Age for Car Instance:
- We called
car.getAge(currentYear)
, which used theyear
property of theCar
instance to calculate the car’s age. - This verified that
Car
correctly inherited thegetAge
method fromVehicle
, allowing it to use theyear
of the car and the current year to calculate the car’s age.
- We called
-
Calculated Age for Vehicle Instance (Truck):
- We called
truck.getAge(currentYear)
, which used theyear
property of theVehicle
instance (the truck) to calculate its age. - This confirmed that instances of the
Vehicle
class can directly use thegetAge
method, proving that it functions as expected for objects created from theVehicle
base class.
- We called
-
Ran the Code to Confirm Expected Output:
- After adding the final test code, we ran the file using
node index.js
in the terminal. - The console output displayed the correct age of each vehicle based on the current year, confirming that
getAge
is working correctly for bothCar
andVehicle
instances.
- After adding the final test code, we ran the file using
This final test demonstrated that the getAge
method is effective across both the base class (Vehicle
) and subclass (Car
), accurately calculating and logging each vehicle's age based on its year
property and the current year.
In this final step, you’ll commit your changes and push your project to GitHub to save and share your work. This will ensure that your project is versioned and backed up remotely.
-
Initialize Git (if not already initialized):
git init
-
Add All Changes to Staging:
git add .
-
Commit Your Changes:
git commit -m "Add JavaScript classes with inheritance and final testing"
-
Connect to Your GitHub Repository (if not already connected):
- Replace
<username>
with your GitHub username and<repository-name>
with the name of your repository.
git remote add origin https://github.com/<username>/<repository-name>.git
- Replace
-
Push to GitHub:
git push -u origin main
In this step, we committed our changes and pushed the project to GitHub to create a backup and enable version control:
- Initialized Git: We ran
git init
to start version control in the project folder if it wasn’t already initialized. - Added Changes to Staging: The
git add .
command staged all project files for the commit. - Committed the Changes: We used
git commit
with a descriptive message to save our work. - Connected to GitHub: We linked our local repository to a GitHub repository using
git remote add origin
. - Pushed to GitHub: Finally,
git push -u origin main
uploaded our code to themain
branch on GitHub.
This step ensures that your project is versioned and safely stored in GitHub, enabling easy sharing and tracking of changes.
In this lab, you learned how to use JavaScript classes with inheritance to create structured, reusable code that models complex, real-world relationships between objects with shared and unique properties and methods. By following each step, you gained practical experience with:
- Defining base classes and using constructors to initialize objects with predefined values.
- Creating subclasses with inheritance to extend base classes, reusing core properties and methods while adding unique functionality.
- Implementing and calling class methods to modify and retrieve object properties, using both inherited and subclass-specific methods.
- Creating multiple instances of classes like
Employee
andCar
, each maintaining independent properties, behaviors, and states.
- Object-Oriented Design with Inheritance: Inheritance allows you to build hierarchical relationships between classes, enhancing code reuse and organization. By defining shared properties in base classes, subclasses can be extended with unique features without duplicating code.
- Method Overriding and Unique Behaviors: Methods like
greet
,work
, andpaint
showcase how subclasses can override and extend base class functionality, allowing instances to respond with specific, tailored behaviors. - Flexible Calculations with Parameters: Using methods like
getAge(currentYear)
illustrates how class methods can interact with both internal properties and external parameters, making your code responsive to real-time data and calculations.
With these skills, you’re now equipped to use JavaScript classes with inheritance to build organized, scalable applications that effectively represent relationships between different types of objects. Continue practicing by creating more complex classes and exploring how inheritance enhances code readability, maintainability, and scalability!
🛑 Only use this as a reference 🛑
💾 Not something to copy and paste 💾
Note: This lab references a solution file located here (link not shown).
© All rights reserved to ThriveDX