Why Use Getters and Setters in JavaScript and TypeScript?
Posted by Nuno Marques on 13 Feb 2023
Getters and setters are often overlooked in JavaScript and TypeScript, but they can be powerful tools when used correctly. They help encapsulate data, control access to properties, and add logic when getting or setting a value. But do you always need them? Letβs explore their use cases, benefits, performance considerations, alternatives, and when to avoid them.
Estimated reading time: 8 min
What Are Getters and Setters?
In JavaScript and TypeScript, getters and setters allow you to define computed properties that are accessed like regular properties but execute logic under the hood.
Basic Example
class Person {
private _name: string;
constructor(name: string) {
this._name = name;
}
get name(): string {
return this._name;
}
set name(newName: string) {
if (!newName) {
throw new Error("Name cannot be empty");
}
this._name = newName;
}
}
const person = new Person("Alice");
console.log(person.name); // Alice
person.name = "Bob";
console.log(person.name); // Bob
Here, name
looks like a regular property but is actually a method executing logic when accessed or modified.
Why Use Getters and Setters?
1. Encapsulation & Data Protection
Getters and setters allow you to hide the internal implementation details of a class while providing controlled access.
Example:
class BankAccount {
private _balance: number = 1000;
get balance(): number {
return this._balance; // Only allows reading, prevents direct modification
}
}
const account = new BankAccount();
console.log(account.balance); // 1000
account.balance = 5000; // β Error (if no setter is defined)
Here, direct modification is blocked, ensuring data integrity.
2. Computed Properties
You can derive values dynamically rather than storing them in separate properties.
Example:
class Rectangle {
constructor(private width: number, private height: number) {}
get area(): number {
return this.width * this.height;
}
}
const rect = new Rectangle(10, 20);
console.log(rect.area); // 200
Instead of storing the area
, we compute it dynamically.
3. Input Validation
You can add checks before allowing a property to change.
Example:
class Temperature {
private _celsius: number;
constructor(celsius: number) {
this._celsius = celsius;
}
get fahrenheit(): number {
return (this._celsius * 9) / 5 + 32;
}
set celsius(value: number) {
if (value < -273.15) {
throw new Error("Temperature below absolute zero is not possible");
}
this._celsius = value;
}
}
const temp = new Temperature(25);
console.log(temp.fahrenheit); // 77
temp.celsius = -300; // β Error
This ensures invalid temperatures cannot be assigned.
4. Lazy Initialization
Instead of computing a value immediately, you can compute it when itβs accessed for the first time.
Example:
class User {
private _cachedData?: string;
get data(): string {
if (!this._cachedData) {
console.log("Fetching data...");
this._cachedData = "User Data";
}
return this._cachedData;
}
}
const user = new User();
console.log(user.data); // Fetching data... User Data
console.log(user.data); // User Data (cached)
This avoids unnecessary computations until needed.
Performance Considerations
Using getters and setters can have a minor impact on performance because they introduce method calls rather than direct property access. While in most cases this is negligible, in performance-critical scenarios, such as real-time applications, physics simulations, or high-frequency game loops, direct property access may be preferable.
Benchmarking Getter/Setter vs. Direct Access
class Test {
private _value: number = 0;
get value(): number {
return this._value;
}
set value(v: number) {
this._value = v;
}
}
const test = new Test();
const iterations = 1_000_000;
console.time("Direct Access");
for (let i = 0; i < iterations; i++) {
test["_value"] = i;
}
console.timeEnd("Direct Access");
console.time("Getter/Setter");
for (let i = 0; i < iterations; i++) {
test.value = i;
}
console.timeEnd("Getter/Setter");
This benchmark helps determine whether getters/setters impact performance significantly in your case.
Alternatives to Getters and Setters
If you need encapsulation but don't want the method call overhead, here are alternatives:
1. Public Properties (Direct Access)
If no logic is required, just use public properties.
class Car {
constructor(public make: string) {}
}
const car = new Car("Toyota");
console.log(car.make);
car.make = "Honda";
2. Using Methods Instead of Getters/Setters
Instead of get
and set
, you can use regular methods.
class Product {
private price: number;
constructor(price: number) {
this.price = price;
}
getPrice(): number {
return this.price;
}
setPrice(newPrice: number) {
if (newPrice < 0) {
throw new Error("Price cannot be negative");
}
this.price = newPrice;
}
}
Using methods makes it clear that you're calling a function, not accessing a property.
Key Takeaways
β Use Getters and Setters When:
- You need encapsulation to prevent direct modifications.
- You want computed properties instead of storing redundant values.
- Validation or formatting is required before setting a value.
- You need lazy initialization for efficiency.
β Avoid Them When:
- A simple public property is enough.
- Performance is critical, and method calls add overhead.
- They overcomplicate the code without adding value.
Final Thoughts
Getters and setters are a great tool, but they arenβt always necessary. Use them when they improve clarity and safety, but avoid them if they make your code unnecessarily complex or slow.