In JavaScript, Getters and Setters are specialized methods that provide a way to define “accessor properties.” They allow you to execute code when a property is read (get) or written to (set), effectively acting as a bridge between a property and a function. This provides a layer of abstraction that is essential for data validation, encapsulation, and creating “computed” properties that don’t actually exist as stored data.
1. Core Concepts: The Accessor Property
Unlike “data properties” which simply hold a value, accessor properties do not contain data. They contain a getter function and/or a setter function.
- Getter (get): A function that returns a value. It is called automatically when the property is accessed. It must have zero parameters.
- Setter (set): A function used to update a value. It is called when a value is assigned to the property. It must have exactly one parameter (the value being assigned).
2. Using Getters and Setters in Object Literals
The simplest way to implement accessors is directly within an object definition using the get and set keywords.
const user = {
firstName: "Jane",
lastName: "Doe",
// The Getter: Computes a value on the fly
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
// The Setter: Deconstructs an input to update internal state
set fullName(value) {
const parts = value.split(" ");
if (parts.length === 2) {
this.firstName = parts[0];
this.lastName = parts[1];
} else {
console.error("Invalid name format");
}
}
};
console.log(user.fullName); // "Jane Doe" (Calls the getter)
user.fullName = "John Smith"; // (Calls the setter)
console.log(user.firstName); // "John"
3. Getters and Setters in ES6 Classes
Classes are the most common environment for accessors. They allow you to protect the internal state of an instance (encapsulation) while providing a clean API to the outside world.
Data Validation Example
Setters are perfect for ensuring that an object never enters an invalid state.
class Thermostat {
constructor(celsius) {
this._celsius = celsius; // Using "_" is a convention for "private" properties
}
get temperature() {
return `${this._celsius}°C`;
}
set temperature(value) {
if (value < -273.15) {
console.error("Temperature below absolute zero is impossible.");
return;
}
this._celsius = value;
}
}
const homeTemp = new Thermostat(20);
homeTemp.temperature = 25; // Valid update
homeTemp.temperature = -500; // Triggers validation error
4. Advanced Use: Data Validation and Protection
Setters act as “gatekeepers” for your data. They ensure that an object never enters an invalid state by sanitizing or rejecting improper inputs.
class BankAccount {
constructor(balance) {
this._balance = balance;
}
get balance() {
return `$${this._balance.toFixed(2)}`;
}
set balance(amount) {
if (amount < 0) {
throw new Error("Balance cannot be negative!");
}
this._balance = amount;
console.log(`Log: Balance updated to ${amount}`);
}
}
5. Defining Accessors via Object.defineProperty
Sometimes you need to add getters or setters to an object that has already been created, or you want more control over property attributes (like making the property non-enumerable).
const profile = { username: "js_dev" };
Object.defineProperty(profile, 'id', {
get() {
return this._id || 0;
},
set(value) {
this._id = value;
},
enumerable: false, // Hidden from loops
configurable: true
});
6. Technical Nuances and “Gotchas”
The Infinite Loop Trap
A common mistake is named the getter/setter the same as the underlying property. This triggers a recursive loop.
- Incorrect: get name() { return this.name; } — Calling person.name calls the getter, which calls person.name, which calls the getter…
- Correct: Use a different internal name, like this._name or the modern private field #name.
Private Fields (#)
In modern environments, you can use True Private Fields to ensure that the internal state cannot be accessed from outside at all, forcing users to go through your getters and setters.
class BankAccount {
#balance = 0; // Truly private
get balance() {
return `Your balance is $${this.#balance}`;
}
set deposit(amount) {
if (amount > 0) this.#balance += amount;
}
}
