JavaScript Getters and Setters Explained
Getters and setters in JavaScript are special methods that define how object properties are accessed and modified. Instead of holding raw data like standard properties, they act as accessor functions bound to a property name. This article explains how getters and setters work in both object literals and ES6 classes, detailing their syntax, practical use cases, and how they enhance data encapsulation and validation.
What Are Getters and Setters?
In JavaScript, object properties are categorized into data properties and accessor properties. Accessor properties do not store values directly; instead, they use:
- Getter (
get): A function that executes automatically when a property is read. It returns a computed or formatted value. - Setter (
set): A function that executes automatically when a property is assigned a value. It allows you to validate, sanitize, or transform the data before updating the underlying state.
Getters and Setters in Object Literals
In an object literal, getters and setters are defined using the
get and set keywords directly before a method
name.
const user = {
firstName: 'Jane',
lastName: 'Doe',
// Getter: computed property
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
// Setter: updates underlying properties
set fullName(name) {
const parts = name.trim().split(' ');
if (parts.length < 2) {
throw new Error('Please provide both first and last names.');
}
this.firstName = parts[0];
this.lastName = parts[1];
}
};
// Accessing the getter (no parentheses required)
console.log(user.fullName); // Output: "Jane Doe"
// Invoking the setter
user.fullName = 'John Smith';
console.log(user.firstName); // Output: "John"
console.log(user.lastName); // Output: "Smith"Getters and Setters in ES6 Classes
In classes, getters and setters provide a clean interface for
managing class fields, especially when enforcing encapsulation with
private fields (prefixed with #) or internal naming
conventions (prefixed with _).
class BankAccount {
#balance = 0; // Private field
constructor(initialBalance) {
this.balance = initialBalance; // Calls the setter
}
// Getter
get balance() {
return this.#balance;
}
// Setter with validation
set balance(amount) {
if (typeof amount !== 'number' || amount < 0) {
throw new Error('Balance must be a positive number.');
}
this.#balance = amount;
}
// Computed getter
get formattedBalance() {
return `$${this.#balance.toFixed(2)}`;
}
}
const account = new BankAccount(100);
console.log(account.balance); // Output: 100
console.log(account.formattedBalance); // Output: "$100.00"
account.balance = 250;
console.log(account.formattedBalance); // Output: "$250.00"
// account.balance = -50; // Throws Error: Balance must be a positive number.Key Reasons to Use Getters and Setters
- Encapsulation and Data Validation: Setters let you enforce business rules and validate incoming values before mutating internal state.
- Computed Properties: Getters derive dynamic values on the fly without duplicating stored state.
- Backward Compatibility: You can turn a regular data property into an accessor property without altering the public API or breaking external code that reads or writes to the property directly.
- Cleaner Syntax: Properties are accessed and
assigned using standard dot notation (
obj.prop = value), avoiding the need for explicit method calls likeobj.getProp()andobj.setProp(value).