Introduction to Classes in JavaScript

This chapter delves into the world of classes in JavaScript, providing a comprehensive guide from fundamental concepts to advanced applications. Classes, introduced in ES6 (ECMAScript 2015), offer a structured approach to object-oriented programming (OOP), promoting code reusability, organization, and maintainability.

Traditional Object Creation (Before Classes)

This section lays the groundwork by explaining how objects were created in JavaScript before the introduction of classes in ES6 (ECMAScript 2015). Two main methods were used:

Object Literal Notation:

  • Curly braces {} are used to define an object.
  • Properties (name-value pairs) representing the object’s attributes are listed within the braces. These properties define the object’s state (data).
  • Methods (functions associated with the object) can be defined directly within the object literal. These methods can access and manipulate the object’s properties using the this keyword, which refers to the current object instance.
				
					console.log("helloword")const person = {
  name: "Alice",
  age: 30,
  greet: function() {
    console.log("Hello, my name is " + this.name);
  }
};

person.greet(); // Output: "Hello, my name is Alice"

				
			

Explanation:

  • Curly braces {} are used to define an object named person.
  • Properties (nameage) and a method (greet) are assigned directly to the object.
  • The greet method is a function defined within the object itself.
  • When person.greet() is called, the greet function is executed, printing the greeting message using the this keyword to access the name property.

Constructor Functions:

  • A constructor function acts as a blueprint for creating objects.
  • It takes arguments representing the initial state (properties) of the object being created.
  • Inside the function, this refers to the newly created object.
  • Properties are assigned to this within the constructor function, initializing the object’s state.
  • The new keyword is used to call the constructor function and create a new object instance.
				
					function Person(name, age) {
  this.name = name;
  this.age = age;
  this.greet = function() {
    console.log("Hello, my name is " + this.name);
  };
}

const alice = new Person("Alice", 30);
alice.greet(); // Output: "Hello, my name is Alice"

				
			

Explanation:

  • A function named Person acts as a constructor, taking arguments (nameage) to represent the object’s initial state.
  • Inside the function, this refers to the newly created object.
  • Properties (nameage) and a method (greet) are assigned to this.
  • The new keyword is used to create a new object (alice) from the Person constructor, passing the arguments to initialize its properties.
  • When alice.greet() is called, the greet method defined within the Person constructor is executed for the specific alice object, printing the greeting with her name.

Limitations of Traditional Approaches:

  • Code repetition: Defining the same properties and methods for each object becomes cumbersome.
  • Less organized: Separating object creation logic from the blueprint can make code harder to read and maintain.

Classes: A Structured Blueprint

This section introduces classes, a more structured approach to object-oriented programming (OOP) in JavaScript. Classes provide a blueprint for creating objects with shared properties and methods.

Definition:

    • The class keyword is used to define a class.
    • A class consists of a constructor (special function) and methods (functions associated with the class).
				
					class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log("Hello, my name is " + this.name);
  }
}

const alice = new Person("Alice", 30);
alice.greet(); // Output: "Hello, my name is Alice"

				
			

Explanation:

  • The class keyword is used to define a class named Person.
  • The constructor method is a special function that is called whenever a new object (instance) is created from the class.
  • Inside the constructor, properties (nameage) are assigned to this, which refers to the newly created object.
  • The greet method is defined outside the constructor but within the class. This method can access the object’s properties defined in the constructor.
  • An object (alice) is created using new Person("Alice", 30). This calls the constructor to initialize alice with the provided name and age.
  • When alice.greet() is called, the greet method defined in the Person class is executed on the alice object, accessing its specific name property.

Benefits of Classes:

  • Code Reusability: A single class can be used to create multiple objects (instances), inheriting the same properties and methods.
  • Organization: Classes separate the blueprint (class definition) from the object creation logic (constructor).
  • Maintainability: Changes made to the class definition affect all instances that inherit from it, promoting consistency and easier updates.

Class Inheritance: Building on Existing Classes

Classes can inherit properties and methods from parent classes, promoting code reuse and creating hierarchies of related objects:

  • A child class (derived class) inherits properties and methods from a parent class (base class) using the extends keyword.
  • The child class can add its own properties and methods, specializing the functionality of the parent class.
  • This creates a hierarchy of related objects, where child classes inherit from parent classes, promoting code reusability and code organization.
				
					class Employee extends Person {
  constructor(name, age, title, salary) {
    super(name, age); // Call the parent class constructor
    this.title = title;
    this.salary = salary;
  }

  getSalary() {
    console.log("Salary: $" + this.salary);
  }
}

const bob = new Employee("Bob", 35, "Software Engineer", 80000);
bob.greet(); // Output: "Hello, my name is Bob"
bob.getSalary(); // Output: "Salary: $80000"

				
			

Explanation:

  • The Employee class inherits from the Person class using the extends Person syntax.
  • The constructor of the Employee class calls the parent class constructor (super(name, age)) to ensure that inherited properties like name and age are initialized for Employee objects.
  • The Employee class adds its own properties (titlesalary) and a new method (getSalary).
  • An object (bob) is created from the Employee class, inheriting properties and methods from Person.
  • bob can access inherited methods (greet) and its own methods (getSalary).

Static Methods and Properties (Utility Functions)

Classes can define static methods and properties that are accessible without creating an object instance:

  • Static methods and properties are associated with the class itself, not with individual object instances.
  • They are accessed using the class name (e.g., MathUtils.add), not through object instances.
  • Static members are useful for utility functions or constants that don’t require object-specific behavior.
				
					class MathUtils {
  static add(x, y) {
    return x + y;
  }

  static PI = 3.14159;
}

console.log(MathUtils.add(5, 3)); // Output: 8
console.log(MathUtils.PI);        // Output: 3.14159

				
			

Explanation:

  • The MathUtils class defines two static members:
    • static add(x, y): This is a static method accessible using the class name (MathUtils.add). It takes two arguments and returns their sum.
    • static PI = 3.14159: This is a static property accessible using the class name (MathUtils.PI). It holds the value of pi.
  • Static members are not tied to individual object instances. They are used for utility functions or constants that don’t require object-specific behavior.

Getters and Setters: Controlling Property Access

  • Getters and setters are methods associated with a class property.
  • A getter method allows retrieving the value of a property.
  • A setter method allows setting the value of a property, potentially with data validation or other logic.
				
					class Person {
  constructor(name) {
    this._fullName = name; // Private property (convention)
  }

  get fullName() {
    return this._fullName;
  }

  set fullName(newName) {
    if (newName.trim() === "") {
      console.error("Name cannot be empty");
    } else {
      this._fullName = newName;
    }
  }
}

const person = new Person("Alice Bob");
console.log(person.fullName); // Output: "Alice Bob"

person.fullName = ""; // Error: Name cannot be empty
person.fullName = "Charlie Brown";
console.log(person.fullName); // Output: "Charlie Brown"

				
			

Explanation:

  • The Person class has a private property (_fullName) using the naming convention (starts with an underscore).
  • The get fullName method allows retrieving the value of _fullName.
  • The set fullName method validates the new name before assigning it to _fullName. This enforces data validation rules.

Advanced Topics: Mixins, Decorators, and Private Fields (ES2022+)

Mixins:

Mixins are reusable modules that contain functionality you can “mix in” to multiple classes. They promote code reusability and separation of concerns.

				
					class Person {
  constructor(name) {
    this._fullName = name; // Private property (convention)
  }

  get fullName() {
    return this._fullName;
  }

  set fullName(newName) {
    if (newName.trim() === "") {
      console.error("Name cannot be empty");
    } else {
      this._fullName = newName;
    }
  }
}

const person = new Person("Alice Bob");
console.log(person.fullName); // Output: "Alice Bob"

person.fullName = ""; // Error: Name cannot be empty
person.fullName = "Charlie Brown";
console.log(person.fullName); // Output: "Charlie Brown"

				
			

Explanation:

  1. The Logger object contains a log method for timestamped logging.
  2. The Person and Employee classes have their own functionalities.
  3. We use Object.assign to mix the Logger functionality into both classes by adding its methods to their prototypes.
  4. Now, both alice (Person) and bob (Employee) can use the log method to log messages with timestamps.

Decorators (Experimental):

Decorators (still in the experimental stage) are functions that allow you to modify the behavior of classes, methods, or properties at runtime.

				
					function logExecutionTime(target) {
  const className = target.name; // Get the class name
  const originalMethod = target; // Store the original method

  target = function(...args) {
    const startTime = performance.now();
    const result = originalMethod.apply(this, args);
    const endTime = performance.now();
    console.log(`${className}.${originalMethod.name} execution time: ${(endTime - startTime).toFixed(2)} ms`);
    return result;
  };

  return target;
}

@logExecutionTime // Decorator syntax (experimental)
class Math {
  static add(x, y) {
    return x + y;
  }
}

console.log(Math.add(5, 3)); // Output: 0.01 ms (approximate execution time)

				
			
  1. The logExecutionTime decorator is a function that takes a target (class, method, or property) as an argument.
  2. It retrieves the class name and stores the original method.
  3. It returns a new function that wraps the original method, logging the execution time before and after calling the original method.
  4. The @logExecutionTime syntax is used to decorate the Math.add method (experimental syntax).

Note: Decorators are still under development and might have syntax changes in future JavaScript versions.

Private Fields (ES2022+)

  • Private fields (introduced in ES2022) are properties declared with a leading hash (#) symbol.
  • They are only accessible and modifiable within the class itself, promoting data encapsulation.
				
					class Person {
  #name; // Private field (using # symbol)

  constructor(name) {
    this.#name = name; // Assign to the private field using #
  }

  getName() { // Public getter method
    return this.#name;
  }

  setName(newName) { // Public setter method (optional for validation)
    this.#name = newName;
  }
}

const alice = new Person("Alice");
console.log(alice.getName()); // Output: "Alice" (using the getter)

// alice.#name is not accessible from outside the class!

				
			

Explanation:

  1. The #name property is declared with a leading hash (#) symbol, signifying it’s a private field.
  2. The private field can only be accessed and modified within the class itself.
  3. We provide public getter (getName) and setter (setName) methods (optional) to control access and potentially validate data.

Classes provide a powerful and organized way to create objects in JavaScript. They promote code reusability, maintainability, and better organization of your codebase. By understanding the core concepts, inheritance, advanced features like mixins and decorators (experimental), and private fields (ES2022+), you can leverage classes effectively in your JavaScript applications. Remember, this chapter provides a solid foundation, but you can always explore more advanced topics and best practices as you delve deeper into object-oriented programming in JavaScript. Happy coding !❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India