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
Related Resources
Next Steps
Continue your learning journey:
- ES6 Classes: Syntax and Features
- Inheritance: Extends and Super
- Static Methods and Properties
- Object Composition Patterns
Comments