Classes

Classes are a relatively new feature added in ES6 (ECMAScript 2015). It provides a way to define objects and their behavior using a syntax similar to classes in other OOP languages like Java, Python, etc.


The OOP principles include

  • Inheritance
  • Polymorphism
  • Data Encapsulation
  • Data Abstraction

Class 🗳️

Describes in general what an object is with methods and variables.

  • Class declarations are NOT HOISTED:

    • Meaning declare class first and then access it
    • Otherwise, code like the following will throw a ReferenceError
  • Functions are HOISTED:

    • You can call function first and then declare it
    • No Error Thrown
class Person {
    name;
    age;
    
	// methods
    sayHello() {
        console.log(`Hello, my name is ${this.name} and 
	                 I am ${this.age} years old.`);
    }
}
 

Constructor 📝

  • Useful when creating an object to specify default values of this instance
  • In inheritance, if child class does not provide a constructor, one will be created by default
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}
 
class Student extends Person{
    // No constructor provided then this is created by default
    constructor(...args){
        super(...args);
    }
}

Objects 📦

  • objects are instances of the class.
  • An instance means that an object is created based on the class.
  • Use keyword new to create a new instance of the class
  • Only one constructor is allowed

Instantiating 🆕

let person = new Person('John', 30);
person.sayHello(); 
// output: "Hello, my name is John and I am 30 years old."

toString()

  • Like toString in Java, returns default information when object is printed
  • Symbol is a primitive data type, called special symbol
  • This is a way to add properties to an object
class Person { 
    #age; // private field
    constructor(age) { 
        this.#age = age; // Allowed
    }
    
    #sayHello() { // private method
        console.log(`Hello, I am ${this.#age} years old.`);
    }
 
    [Symbol.toPrimitive]() {
        return `Person name: ${Person.age}`;
    }
}
 
 
let new_car = new Car()
console.log(new_car) // Dealership: Terp Cars

Class Access Modifiers 🔐

Instance Variables

  • Instance Variables are available across all objects but unique for each object.
  • In other words, an instance variable from one object differs from another object. For instance:
student_1.name = "Jose"
student_2.name = "Kevin"
  • Instance Variables has NO TYPES (Javascript)
class Class {
	id;
	name;
	age;
}

Class Variables

  • Class Variable are shared between all objects, using the static keyword.
  • If the class variable from a object is modified, another object of the same class also updates it.
  • All static needs to be called with their Class name, instead of this. i.e line 10
class Car {
    static dealership = "Terp Cars"; // static-public field
    static #instances = 0; // static-private field
 
    info() {
        return `${Car.#printDealer()}`
    }
 
    static #printDealer(){ // can also make static-private functions
	    Car.#instance++;
		return `${Car.dealership}`;
    }
}

Public vs Private Variables

Public

  • In Javascript, methods and data are public by default
  • Public variables are accessible from outside the class and can be accessed with the dot notation (instanceObject.dataField)
class Person { 
    name; // public field
    constructor(name) { 
        this.name = name; // ✅ allowed access
    }
}
let person = new Person("Jose")
student_1.name // ✅ allowed access

Private

  • Private: cannot be accessed outside of class, only accessible from inside the class
  • Defined using the (#) symbol before the variable or function name.
class Person { 
    #age; // private field
    constructor(age) { 
        this.#age = age; // ✅ allowed
    }
    
    #sayHello() { // private method
        console.log(`Hello, I am ${this.#age} years old.`);
    }
}
let person = new Person(30);
console.log(person.age); // ❌ No Allowed since outside of class, Error
person.sayHello() // ❌ No Allowed since private, Error

Wrap Up

  • Public - by default

  • Private - add #

  • Instance member (non static) - only instances objects can call these members

  • Static member - add static, only the Class can call these members

The 4 Principles

OOP allows objects to interact with each other using four basic principles: encapsulation, inheritance, polymorphism, and abstraction.

Inheritance 👨‍👦

  • Inheritance is a way to create a new class that is a modified version of an existing class.
  • In JavaScript, inheritance can be achieved using the extends keyword.
  • Rule: child extends parent class
class Car {
    model;
    year;
    wheels;
    static dealership = "Toyota";
    static #carSold = 0; // shared all instances, no access outside
 
    constructor(model, year, wheels){
        this.model = model;
        this.year = year;
        Car.#carSold++;
    }
	
    // Java's toString()
    [Symbol.toPrimitive]() {
        return `
            Car sold: ${this.model}, ${this.year},
            Car sold by: ${Car.dealership}
        `;
    }
 
    info(){
        document.writeln(`
            ${this.model}, ${this.year}, ${this.wheels}, 
            ${Car.dealership}, ${Car.#carSold}`
        )    
    }
 
    // getter
    get model(){
        return this.model
    }
 
    // setter
    set model(newModel) {
        this.model = newModel;
    }
}
class SportsCar extends Car {
    #engine;
    
    constructor(model, year, wheels engine) {
        super(model, year, wheels);
        this.#engine = engine;
    }
    
    get engine(){ return this.#engine; }
 
    set engine(newEngine){ this.#engine = newEngine; }
 
    // Overrides info() from Car
    info() {
        super.info(); // call parent Class method
    }
 
	// Overrides toString()
    [Symbol.toPrimitive]() {
        return `
            ${super[Symbol.toPrimitive]()}, Engine: ${this.#engine}
        `;
     }
}

Polimorphism 👥

  • Polymorphism is a concept in object-oriented programming that allows objects of different classes to be treated as if they were objects of the same class, as long as they share a common interface or inheritance hierarchy.
  • This means that a method can behave differently depending on the object it is called on.
  • This can be achieved through method overriding.
  • In the example above, the SportsCar class overrides the info() method inherited from Car class, providing a different implementation for sport car. This method behaves differently depending on the object it is called on, even though the method has the same name info().
function carInfo(car) {
  car.info();
}
 
let car = new Car('Corolla', '2001', 4);
let sport = new SportsCar('Camaro', '2023', 6);
 
// Polimorphism
carInfo(car); // output: "Corolla, 2001, 4, Toyota, 1"
carInfo(sport); // output: "Camaro, 2023, 6, Toyota, 2"

In this way, polymorphism allows us to write more flexible and reusable code by creating classes that share a common interface but can have different implementations for their methods.