Thursday, August 25, 2022

Mastering Class Structure: Constructors, Fields, Methods, static, and #private in JavaScript

JavaScript classes offer more than just a cleaner way to write object-oriented code. They define a blueprint for creating objects using a constructor for initialization, fields for storing state, and methods for behavior. Classes can also extend other classes, allowing for inheritance and code reuse in more complex systems. This structure promotes better organization and encapsulation, making your code more predictable and easier to maintain.

The Old Way

ES5 Functional Version

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function() {
  console.log("Hi, I'm " + this.name + " and I'm " + this.age + " years old.");
};

var person1 = new Person("Alice", 30);
person1.greet(); // Hi, I'm Alice and I'm 30 years old.

ES6 Class Version

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old.`);
  }
}

const person1 = new Person("Alice", 30);
person1.greet(); // Hi, I'm Alice and I'm 30 years old.

Constructors: Initializing Object State

A constructor is a special method used to initialize a new instance of a class. When you create a new object with the new keyword, the constructor runs automatically. This is where you typically assign initial values to fields or perform setup logic.

Example:

class BankCustomer {
  constructor(name, accountNumber) {
    this.name = name;
    this.accountNumber = accountNumber;
  }

  greet() {
    console.log(`Welcome, ${this.name}. Your account number is ${this.accountNumber}.`);
  }
}

const customer = new BankCustomer("Jane", "0011223344");
customer.greet(); // Welcome, Jane. Your account number is 0011223344.

Fields: Storing State Within Instances

Fields in JavaScript classes are used to define and store data specific to each instance. Declaring fields makes it easier to see what properties an object will have and can also help enforce default values. Fields can also exist without a constructor when the default values are sufficient.

Example:

class BankBranch {
  name = "Main Street Branch";
  location = "123 Main St";

  info() {
    console.log(`${this.name} is located at ${this.location}.`);
  }
}

const branch = new BankBranch();
branch.info(); // Main Street Branch is located at 123 Main St.

Methods: Defining Behavior

Methods define the behavior of class instances. They are functions declared inside the class body and can operate on instance data using this. Methods help encapsulate logic that belongs to a specific object, improving organization and code reuse.

Example:

class ATM {
  cashAvailable = 1000;

  withdraw(amount) {
    if (amount <= this.cashAvailable) {
      this.cashAvailable -= amount;
      console.log(`Dispensed $${amount}`);
    } else {
      console.log("Insufficient funds in ATM");
    }
  }
}

const atm = new ATM();
atm.withdraw(200);
// Dispensed $200

static: Shared Across All Instances

The static keyword is used to define fields or methods on the class itself, not on the instances created from it. These members are typically utility functions or data shared across all instances.

When to Use:

  • You need a helper function related to the class logic.
  • You want a counter or registry that all instances should access.
  • You don’t need this to refer to an individual object.

Example:

class Bank {
  static totalAccounts = 0;

  constructor() {
    Bank.totalAccounts++;
  }

  static getTotalAccounts() {
    return Bank.totalAccounts;
  }
}

const b1 = new Bank();
const b2 = new Bank();

console.log(Bank.getTotalAccounts()); // 2

#private: True Encapsulation

JavaScript also supports true private fields using the # symbol. These fields and methods are inaccessible outside of the class body and cannot be tampered with accidentally.

When to Use:

  • You want to hide internal details from consumers of the class.
  • You’re implementing sensitive logic or internal bookkeeping.
  • You want to avoid conflicts with subclass implementations.

Example:

class BankAccount {
  #balance = 0;

  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
    }
  }

  getBalance() {
    return this.#balance;
  }
}

const acc = new BankAccount();
acc.deposit(100);
console.log(acc.getBalance()); // 100

Combining Static and Private

You can use static and private features together for utility and internal state management.

Example:

class BankingSession {
  static #sessions = new Map();

  static start(sessionId) {
    if (!this.#sessions.has(sessionId)) {
      this.#sessions.set(sessionId, new BankingSession(sessionId));
    }
    return this.#sessions.get(sessionId);
  }

  #id;

  constructor(sessionId) {
    this.#id = sessionId;
  }

  getSessionId() {
    return this.#id;
  }
}

const s1 = BankingSession.start("session-abc");
const s2 = BankingSession.start("session-abc");
console.log(s1 === s2); // true

Summary

  • Use constructors to initialize new instances and assign default state.
  • Use fields to define and store instance-specific data, either inline or within the constructor.
  • Use methods to encapsulate behavior and make objects interactive.
  • Use static when the data or method applies to the class as a whole, not an individual object.
  • Use #private when data or methods shouldn’t be accessed or modified from outside the class.
  • Combine static and private members to manage internal state and enforce encapsulation across class-level utilities.

Use these features together to write safer, more expressive class-based code in modern JavaScript.

No comments:

Post a Comment