Skip to main content
โšก Calmops

Getters and Setters in JavaScript

Getters and Setters in JavaScript

Introduction

Getters and setters are special methods that allow you to define custom behavior when reading and writing object properties. They provide a way to encapsulate data, validate input, and compute values on-the-fly. Understanding how to use getters and setters effectively is crucial for writing clean, maintainable object-oriented JavaScript code.

In this article, you’ll learn how to create and use getters and setters, implement validation, and explore best practices for property access.

Understanding Getters and Setters

Basic Getter

A getter is a method that gets the value of a property. It’s defined using the get keyword.

// Basic getter
class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  
  // Getter
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const person = new Person('John', 'Doe');
console.log(person.fullName); // 'John Doe' (called like a property, not a method)

Basic Setter

A setter is a method that sets the value of a property. It’s defined using the set keyword.

// Basic setter
class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  
  // Setter
  set fullName(name) {
    const [first, last] = name.split(' ');
    this.firstName = first;
    this.lastName = last;
  }
}

const person = new Person('John', 'Doe');
person.fullName = 'Jane Smith'; // Called like a property assignment
console.log(person.firstName); // 'Jane'
console.log(person.lastName); // 'Smith'

Getter and Setter Together

// Getter and setter working together
class Temperature {
  constructor(celsius) {
    this._celsius = celsius; // Private property (convention)
  }
  
  // Getter
  get celsius() {
    return this._celsius;
  }
  
  // Setter
  set celsius(value) {
    this._celsius = value;
  }
  
  // Computed getter
  get fahrenheit() {
    return (this._celsius * 9/5) + 32;
  }
  
  // Computed setter
  set fahrenheit(value) {
    this._celsius = (value - 32) * 5/9;
  }
}

const temp = new Temperature(0);
console.log(temp.celsius); // 0
console.log(temp.fahrenheit); // 32

temp.fahrenheit = 212;
console.log(temp.celsius); // 100
console.log(temp.fahrenheit); // 212

Validation with Setters

Input Validation

// Validate input in setter
class User {
  constructor(email) {
    this._email = '';
    this.email = email; // Use setter for validation
  }
  
  get email() {
    return this._email;
  }
  
  set email(value) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(value)) {
      throw new Error('Invalid email format');
    }
    this._email = value;
  }
}

const user = new User('[email protected]');
console.log(user.email); // '[email protected]'

try {
  user.email = 'invalid-email';
} catch (error) {
  console.error(error.message); // 'Invalid email format'
}

Range Validation

// Validate range in setter
class BankAccount {
  constructor(balance) {
    this._balance = 0;
    this.balance = balance;
  }
  
  get balance() {
    return this._balance;
  }
  
  set balance(value) {
    if (value < 0) {
      throw new Error('Balance cannot be negative');
    }
    if (!Number.isFinite(value)) {
      throw new Error('Balance must be a valid number');
    }
    this._balance = value;
  }
  
  deposit(amount) {
    this.balance = this._balance + amount;
  }
  
  withdraw(amount) {
    if (amount > this._balance) {
      throw new Error('Insufficient funds');
    }
    this.balance = this._balance - amount;
  }
}

const account = new BankAccount(1000);
console.log(account.balance); // 1000

account.deposit(500);
console.log(account.balance); // 1500

account.withdraw(200);
console.log(account.balance); // 1300

try {
  account.balance = -100;
} catch (error) {
  console.error(error.message); // 'Balance cannot be negative'
}

Computed Properties

Computed Getter

// Computed property that calculates on-the-fly
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
  
  get area() {
    return this.width * this.height;
  }
  
  get perimeter() {
    return 2 * (this.width + this.height);
  }
  
  get isSquare() {
    return this.width === this.height;
  }
}

const rect = new Rectangle(10, 5);
console.log(rect.area); // 50
console.log(rect.perimeter); // 30
console.log(rect.isSquare); // false

const square = new Rectangle(5, 5);
console.log(square.isSquare); // true

Computed Setter

// Setter that computes and stores related values
class Circle {
  constructor(radius) {
    this._radius = radius;
  }
  
  get radius() {
    return this._radius;
  }
  
  set radius(value) {
    if (value <= 0) {
      throw new Error('Radius must be positive');
    }
    this._radius = value;
  }
  
  get diameter() {
    return this._radius * 2;
  }
  
  set diameter(value) {
    this._radius = value / 2;
  }
  
  get area() {
    return Math.PI * this._radius ** 2;
  }
  
  get circumference() {
    return 2 * Math.PI * this._radius;
  }
}

const circle = new Circle(5);
console.log(circle.radius); // 5
console.log(circle.diameter); // 10
console.log(circle.area); // 78.53981633974483

circle.diameter = 20;
console.log(circle.radius); // 10
console.log(circle.area); // 314.1592653589793

Practical Real-World Examples

Example 1: Product Class with Pricing

// Product with computed pricing
class Product {
  constructor(name, basePrice, taxRate = 0.1) {
    this.name = name;
    this._basePrice = basePrice;
    this.taxRate = taxRate;
    this._discount = 0;
  }
  
  get basePrice() {
    return this._basePrice;
  }
  
  set basePrice(value) {
    if (value < 0) {
      throw new Error('Price cannot be negative');
    }
    this._basePrice = value;
  }
  
  get discount() {
    return this._discount;
  }
  
  set discount(value) {
    if (value < 0 || value > 100) {
      throw new Error('Discount must be between 0 and 100');
    }
    this._discount = value;
  }
  
  get discountedPrice() {
    return this._basePrice * (1 - this._discount / 100);
  }
  
  get tax() {
    return this.discountedPrice * this.taxRate;
  }
  
  get totalPrice() {
    return this.discountedPrice + this.tax;
  }
  
  getDetails() {
    return {
      name: this.name,
      basePrice: this._basePrice.toFixed(2),
      discount: `${this._discount}%`,
      discountedPrice: this.discountedPrice.toFixed(2),
      tax: this.tax.toFixed(2),
      totalPrice: this.totalPrice.toFixed(2)
    };
  }
}

const product = new Product('Laptop', 1000);
console.log(product.getDetails());

product.discount = 10;
console.log(product.getDetails());

Example 2: User Profile with Validation

// User profile with validated properties
class UserProfile {
  constructor(username, email, age) {
    this._username = '';
    this._email = '';
    this._age = 0;
    
    this.username = username;
    this.email = email;
    this.age = age;
  }
  
  get username() {
    return this._username;
  }
  
  set username(value) {
    if (value.length < 3) {
      throw new Error('Username must be at least 3 characters');
    }
    if (!/^[a-zA-Z0-9_]+$/.test(value)) {
      throw new Error('Username can only contain letters, numbers, and underscores');
    }
    this._username = value;
  }
  
  get email() {
    return this._email;
  }
  
  set email(value) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(value)) {
      throw new Error('Invalid email format');
    }
    this._email = value;
  }
  
  get age() {
    return this._age;
  }
  
  set age(value) {
    if (!Number.isInteger(value) || value < 0 || value > 150) {
      throw new Error('Age must be a valid number between 0 and 150');
    }
    this._age = value;
  }
  
  get isAdult() {
    return this._age >= 18;
  }
  
  get profile() {
    return {
      username: this._username,
      email: this._email,
      age: this._age,
      isAdult: this.isAdult
    };
  }
}

const user = new UserProfile('john_doe', '[email protected]', 25);
console.log(user.profile);

try {
  user.username = 'ab'; // Too short
} catch (error) {
  console.error(error.message);
}

Example 3: Date/Time Wrapper

// Date wrapper with convenient getters
class DateWrapper {
  constructor(date = new Date()) {
    this._date = new Date(date);
  }
  
  get date() {
    return this._date;
  }
  
  set date(value) {
    this._date = new Date(value);
  }
  
  get year() {
    return this._date.getFullYear();
  }
  
  set year(value) {
    this._date.setFullYear(value);
  }
  
  get month() {
    return this._date.getMonth() + 1; // 1-12
  }
  
  set month(value) {
    this._date.setMonth(value - 1);
  }
  
  get day() {
    return this._date.getDate();
  }
  
  set day(value) {
    this._date.setDate(value);
  }
  
  get hour() {
    return this._date.getHours();
  }
  
  set hour(value) {
    this._date.setHours(value);
  }
  
  get minute() {
    return this._date.getMinutes();
  }
  
  set minute(value) {
    this._date.setMinutes(value);
  }
  
  get formattedDate() {
    return this._date.toLocaleDateString();
  }
  
  get formattedTime() {
    return this._date.toLocaleTimeString();
  }
  
  get formattedDateTime() {
    return this._date.toLocaleString();
  }
  
  get dayOfWeek() {
    const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    return days[this._date.getDay()];
  }
}

const dateWrapper = new DateWrapper('2025-12-18');
console.log(dateWrapper.formattedDate); // '12/18/2025'
console.log(dateWrapper.dayOfWeek); // 'Thursday'

dateWrapper.month = 1;
dateWrapper.day = 1;
console.log(dateWrapper.formattedDate); // '1/1/2025'

Example 4: Shopping Cart with Computed Totals

// Shopping cart with computed totals
class ShoppingCart {
  constructor(taxRate = 0.08) {
    this.items = [];
    this.taxRate = taxRate;
  }
  
  addItem(name, price, quantity = 1) {
    const item = this.items.find(i => i.name === name);
    if (item) {
      item.quantity += quantity;
    } else {
      this.items.push({ name, price, quantity });
    }
  }
  
  removeItem(name) {
    this.items = this.items.filter(i => i.name !== name);
  }
  
  get subtotal() {
    return this.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  }
  
  get tax() {
    return this.subtotal * this.taxRate;
  }
  
  get total() {
    return this.subtotal + this.tax;
  }
  
  get itemCount() {
    return this.items.reduce((sum, item) => sum + item.quantity, 0);
  }
  
  get isEmpty() {
    return this.items.length === 0;
  }
  
  getSummary() {
    return {
      items: this.items,
      itemCount: this.itemCount,
      subtotal: this.subtotal.toFixed(2),
      tax: this.tax.toFixed(2),
      total: this.total.toFixed(2)
    };
  }
}

const cart = new ShoppingCart();
cart.addItem('Laptop', 999.99, 1);
cart.addItem('Mouse', 29.99, 2);
cart.addItem('Keyboard', 79.99, 1);

console.log(cart.getSummary());

Getters and Setters with Inheritance

// Parent class with getters/setters
class Animal {
  constructor(name) {
    this._name = name;
  }
  
  get name() {
    return this._name;
  }
  
  set name(value) {
    if (value.length === 0) {
      throw new Error('Name cannot be empty');
    }
    this._name = value;
  }
}

// Child class extending getters/setters
class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this._breed = breed;
  }
  
  get breed() {
    return this._breed;
  }
  
  set breed(value) {
    if (value.length === 0) {
      throw new Error('Breed cannot be empty');
    }
    this._breed = value;
  }
  
  get info() {
    return `${this._name} is a ${this._breed}`;
  }
}

const dog = new Dog('Rex', 'Labrador');
console.log(dog.info); // 'Rex is a Labrador'

dog.name = 'Max';
dog.breed = 'Golden Retriever';
console.log(dog.info); // 'Max is a Golden Retriever'

Common Mistakes to Avoid

Mistake 1: Infinite Recursion

// โŒ Wrong - Infinite recursion
class Person {
  get name() {
    return this.name; // Calls itself infinitely
  }
}

// โœ… Correct - Use private property
class Person {
  constructor(name) {
    this._name = name;
  }
  
  get name() {
    return this._name;
  }
}

Mistake 2: Forgetting to Use Private Property Convention

// โŒ Wrong - No private property
class Temperature {
  get celsius() {
    return this.celsius; // Infinite recursion
  }
}

// โœ… Correct - Use underscore convention
class Temperature {
  constructor(celsius) {
    this._celsius = celsius;
  }
  
  get celsius() {
    return this._celsius;
  }
}

Mistake 3: Not Validating in Setters

// โŒ Wrong - No validation
class Age {
  set age(value) {
    this._age = value; // No validation
  }
}

// โœ… Correct - Validate input
class Age {
  set age(value) {
    if (value < 0 || value > 150) {
      throw new Error('Invalid age');
    }
    this._age = value;
  }
}

Mistake 4: Side Effects in Getters

// โŒ Wrong - Getter with side effects
class Counter {
  get count() {
    this._count++; // Unexpected side effect
    return this._count;
  }
}

// โœ… Correct - Getter without side effects
class Counter {
  get count() {
    return this._count;
  }
  
  increment() {
    this._count++;
  }
}

Summary

Getters and setters provide powerful encapsulation:

  • Getters allow computed properties and controlled access
  • Setters enable validation and side effects
  • Use underscore convention for private properties
  • Validate input in setters
  • Avoid side effects in getters
  • Use getters/setters for computed properties
  • Getters and setters work with inheritance
  • They provide a clean API for object properties

Next Steps

Continue your learning journey:

Comments