Object-Oriented Programming
Object-Oriented Programming (OOP) is a programming methodology that utilizes objects and their interrelationships to model and solve problems. In JavaScript, objects can be used to represent complex data structures and function as classes that can be instantiated and inherit properties.
The ‘new’ classes
A class in JS is not actually a class in the traditional sense; it is a “constructor function”.
- JavaScript is prototype-based when it comes to OOP.
- Beware of the ECMA version!
is equivalent to:
Typically, one would use a transpiler like TypeScript. However, for the sake of clarity, the syntax is as follows:
Constructors and methods
In JavaScript, constructors and methods within classes offer a streamlined way to create and manage objects. Constructors are special functions invoked when a new instance of a class is created, initializing the object with properties. Methods, on the other hand, are functions associated with the object, allowing interaction or manipulation of the object’s data.
Here is an example of a class with a constructor and a method:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// Getter method for name
getName() {
return this.name;
}
// Method to celebrate birthday and age increment
celebrateBirthday() {
this.age += 1;
console.log(`Happy Birthday ${this.name}! You are now ${this.age} years old.`);
}
}
let person = new Person("Alice", 30);
console.log(person.getName()); // Alice
person.celebrateBirthday(); // Happy Birthday Alice! You are now 31 years old.
Static methods
Static methods are methods that are associated with the class itself rather than with any specific instance of the class - example:
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}
print() {
console.log(`My name is ${this._name} and I am ${this._age} years old`);
}
static createRandomPerson() {
let number = Math.floor(Math.random() * 25 + 1);
return new Person(String.fromCharCode(number + 65), number);
}
}
let p = Person.createRandomPerson();
p.print();
Encapsulation
In modern JavaScript OOP, utilizing private fields enhances encapsulation and data protection within classes. Private fields are declared with a # prefix, restricting access to the class itself. This feature ensures better control over how properties are accessed and modified using get/set, reinforcing the principles of encapsulation.
class Person {
#name;
#age;
#myPrivateField;
constructor(name, age) {
this.#name = name;
this.#age = age;
this.#myPrivateField = "this is a private field";
}
print() {
console.log(`My name is ${this.#name} and I am ${this.#age} years old`);
}
// Getter and setter for name, demonstrating controlled access to private fields
get name() {
return this.#name;
}
set name(value) {
this.#name = value;
}
}
let p = new Person("Mat", 13);
p.print(); // Outputs: My name is Mat and I am 13 years old
p.name = "Mathias";
p.print(); // Outputs: My name is Mathias and I am 13 years old
console.log(p.myPrivateField); // undefined
Using private fields in JavaScript, denoted by #, enhances object-oriented programming by encapsulating data. This encapsulation allows for controlled access to an object’s properties, ensuring that they can only be directly manipulated within the class itself. Getters and setters are then used to provide a controlled interface to these private fields, allowing for validation, logging, or other operations before accessing or modifying the data. This practice promotes cleaner, more secure code by preventing accidental or unauthorized modifications to critical class properties.
Inheritance
Inheritance in Object-Oriented Programming (OOP) is a fundamental concept that enables a class to inherit properties and methods from another class. This mechanism promotes code reusability and establishes a parent-child relationship between classes. In JavaScript, inheritance allows for the creation of a hierarchical class structure where subclasses (child classes) inherit characteristics from their superclasses (parent classes), while also having the ability to introduce their own unique attributes and behaviors.
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}
print() {
console.log(`My name is ${this._name} and I am ${this._age} years old`);
}
}
class Student extends Person {
constructor(name, age, studentId) {
super(name, age);
this._studentId = studentId;
}
print() {
console.log(
`My name is ${this._name} and I am ${this._age} years old with student id ${this._studentId}`
);
}
}
let s = new Student("Villads", 8, "xyz");
s.print(); // My name is Villads and I am 8 years old with student id xyz
The example you provided demonstrates a simple inheritance scenario with two classes:
-
Person Class: This is the base class (or superclass) that defines properties and methods common to all persons, such as name and age, along with a print method to output these details.
-
Student Class: This subclass (or child class) extends Person to include specific characteristics related to students, such as studentId. It also overrides the print method to include studentId in the output.
This inheritance structure allows Student to automatically inherit the properties (_name and _age) and methods from Person, while also defining additional properties and methods specific to students.
To elaborate on this concept, let’s introduce a new subclass Teacher that also extends Person, demonstrating how multiple classes can inherit from a single superclass, each adding their own unique traits.
class Teacher extends Person {
constructor(name, age, subject) {
super(name, age); // Call the superclass constructor with the name and age
this._subject = subject; // New property specific to Teacher
}
print() {
console.log(`My name is ${this._name}, I am ${this._age} years old, and I teach ${this._subject}.`);
}
}
Now, we can create instances of both Student and Teacher, highlighting the versatility of inheritance:
let student = new Student("Emma", 20, "s123");
student.print(); // Outputs: My name is Emma and I am 20 years old with student id s123
let teacher = new Teacher("John", 45, "Mathematics");
teacher.print(); // Outputs: My name is John, I am 45 years old, and I teach Mathematics.
Key Concepts:
- Extensibility: Inheritance makes it easy to extend existing code. As new classes of people are needed (like Teacher), they can be added without modifying the existing Person class.
- Override Methods: Subclasses can override methods inherited from the superclass to provide specialized behavior, as seen with the print method in both Student and Teacher.
- Use of super: The super keyword is used to call functions on a parent object, notably to invoke the superclass’s constructor from the subclass constructor.
Inheritance is a powerful tool in OOP, fostering a clean, organized structure for code by allowing classes to build upon existing behaviors while introducing new ones. This promotes code reusability and scalability, making it easier to manage and expand upon complex systems.
Summary
Object-Oriented Programming in JavaScript provides a structured approach to organizing code through classes, encapsulation, and inheritance.
Key Takeaways
Classes in JavaScript:
- JavaScript classes are constructor functions with syntactic sugar
- JavaScript is prototype-based, not class-based like traditional OOP languages
- Modern ES6 class syntax is preferred over older constructor function patterns
Constructors and Methods:
- Constructors initialize new instances with properties
- Methods are functions associated with class instances
- Use
thisto reference instance properties and methods
Static Methods:
- Static methods belong to the class itself, not instances
- Called on the class directly:
ClassName.staticMethod() - Useful for utility functions and factory methods
Encapsulation:
- Private fields use
#prefix (ES2022 feature) - Private fields can only be accessed within the class
- Getters and setters provide controlled access to private fields
- Promotes data protection and prevents unauthorized modifications
Inheritance:
- Classes can extend other classes using
extendskeyword super()calls the parent class constructor- Subclasses inherit properties and methods from parent classes
- Methods can be overridden in subclasses for specialized behavior
- Promotes code reusability and hierarchical organization
Best Practices:
- Use private fields for sensitive data
- Provide getters/setters for controlled property access
- Use
super()in subclass constructors before accessingthis - Override methods when subclasses need specialized behavior
- Use static methods for class-level utilities
Understanding OOP in JavaScript enables you to write more organized, maintainable, and scalable code by leveraging encapsulation, inheritance, and polymorphism.