JavaScript Getters, Setters & Encapsulation
Control access to object properties and validate data using getters and setters.
Basic Getters and Setters
class Temperature {
#celsius;
constructor(celsius) { this.celsius = celsius; }
get celsius() { return this.#celsius; }
set celsius(value) {
if (value < -273.15) throw new Error("Below absolute zero!");
this.#celsius = value;
}
get fahrenheit() { return (this.#celsius * 9/5) + 32; }
set fahrenheit(value) { this.celsius = (value - 32) * 5/9; }
}
let temp = new Temperature(100);
console.log(temp.fahrenheit); // 212
temp.fahrenheit = 32;
console.log(temp.celsius); // 0
get— Runs when you read the propertyset— Runs when you assign a value
Validation in Setters
class User {
#name;
#email;
constructor(name, email) { this.name = name; this.email = email; }
get name() { return this.#name; }
set name(v) {
if (!v || v.trim().length < 2) throw new Error("Name too short");
this.#name = v.trim();
}
get email() { return this.#email; }
set email(v) {
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)) throw new Error("Invalid email");
this.#email = v.toLowerCase();
}
}
let user = new User("John", "[email protected]");
console.log(user.email); // "[email protected]"
Remember: Setters run on assignment — this.name = "J" triggers the setter.
Computed Properties
Getters calculate values on demand — always in sync:
class Rectangle {
#w; #h;
constructor(w, h) { this.w = w; this.h = h; }
get w() { return this.#w; }
set w(v) { if (v <= 0) throw new Error("Must be positive"); this.#w = v; }
get h() { return this.#h; }
set h(v) { if (v <= 0) throw new Error("Must be positive"); this.#h = v; }
get area() { return this.#w * this.#h; }
get isSquare() { return this.#w === this.#h; }
}
let rect = new Rectangle(4, 6);
console.log(rect.area); // 24
console.log(rect.isSquare); // false
Tip: Computed properties stay in sync — no need to update them when data changes.
Complete Example: Bank Account
class BankAccount {
#balance;
#transactions = [];
constructor(owner, initialBalance = 0) {
this.owner = owner;
this.#balance = initialBalance;
}
get balance() { return this.#balance; }
get transactions() { return [...this.#transactions]; }
deposit(amount) {
if (amount <= 0) throw new Error("Amount must be positive");
this.#balance += amount;
this.#transactions.push({ type: "deposit", amount });
return this;
}
withdraw(amount) {
if (amount <= 0) throw new Error("Amount must be positive");
if (amount > this.#balance) throw new Error("Insufficient funds");
this.#balance -= amount;
this.#transactions.push({ type: "withdrawal", amount });
return this;
}
}
let account = new BankAccount("John", 1000);
account.deposit(500).withdraw(200);
console.log(account.balance); // 1300
Remember: transactions returns a copy — external code can’t mutate the internal array.
Best Practices
- Use getters for computed values — Calculate on demand
- Use setters for validation — Protect data integrity
- Use private fields — Encapsulate implementation details
- Return copies from getters — Prevent external mutation
Common Mistakes
- Infinite recursion — A getter calling itself
- Forgetting validation — Invalid data enters the object
- Mutating returned objects — Return copies instead
- Not handling null/undefined — Check in setters