JavaScript Getters and Setters
Introduction
In JavaScript, objects are collections of key-value pairs where values can be accessed directly through their corresponding keys. However, sometimes we need more control over how properties are accessed or modified. This is where getters and setters come into play.
Getters and setters are special methods that allow you to define how object properties are accessed (get) and modified (set). They enable you to:
- Execute code when properties are read or written
- Validate data before assigning it to a property
- Compute values on-the-fly
- Implement data encapsulation and hide internal implementation details
Let's dive into how they work and why they're useful for JavaScript developers.
Understanding Getters
A getter is a method that gets the value of a specific property. When you access the property, the getter function is called, and the return value of the function becomes the value of the property.
Basic Getter Syntax
There are two ways to define getters in JavaScript:
1. Using the get
keyword in an object literal:
const person = {
firstName: "John",
lastName: "Doe",
// Getter
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
};
console.log(person.fullName); // Output: John Doe
Notice that when accessing person.fullName
, we don't use parentheses like a normal function call. The getter is accessed as if it were a regular property.
2. Using Object.defineProperty()
:
const person = {
firstName: "John",
lastName: "Doe"
};
Object.defineProperty(person, 'fullName', {
get: function() {
return `${this.firstName} ${this.lastName}`;
}
});
console.log(person.fullName); // Output: John Doe
Understanding Setters
A setter is a method that sets the value of a specific property. When you assign a value to the property, the setter function is called with the new value as an argument.
Basic Setter Syntax
Similar to getters, there are two ways to define setters:
1. Using the set
keyword in an object literal:
const person = {
firstName: "John",
lastName: "Doe",
set fullName(name) {
const parts = name.split(' ');
this.firstName = parts[0];
this.lastName = parts[1] || '';
}
};
person.fullName = "Jane Smith";
console.log(person.firstName); // Output: Jane
console.log(person.lastName); // Output: Smith
2. Using Object.defineProperty()
:
const person = {
firstName: "John",
lastName: "Doe"
};
Object.defineProperty(person, 'fullName', {
set: function(name) {
const parts = name.split(' ');
this.firstName = parts[0];
this.lastName = parts[1] || '';
}
});
person.fullName = "Jane Smith";
console.log(person.firstName); // Output: Jane
console.log(person.lastName); // Output: Smith
Combining Getters and Setters
Getters and setters are often used together to create a "pseudo-property" that behaves like a regular property but has custom access control:
const circle = {
_radius: 5, // Convention: underscore indicates private property
get radius() {
return this._radius;
},
set radius(value) {
if (value <= 0) {
throw new Error('Radius must be positive');
}
this._radius = value;
},
get area() {
return Math.PI * this._radius * this._radius;
}
};
console.log(circle.radius); // Output: 5
console.log(circle.area); // Output: 78.53981633974483
circle.radius = 10;
console.log(circle.area); // Output: 314.1592653589793
// This would throw an error:
// circle.radius = -5;
In this example:
_radius
is the actual stored property (with the underscore convention indicating it's "private")radius
is a getter/setter pair that controls access to_radius
area
is a read-only computed property (only has a getter)
Practical Applications of Getters and Setters
1. Data Validation
Setters are excellent for validating data before assigning it:
const user = {
_email: '',
get email() {
return this._email;
},
set email(value) {
// Simple email validation
if (!/^\S+@\S+\.\S+$/.test(value)) {
throw new Error('Invalid email format');
}
this._email = value;
}
};
user.email = "[email protected]"; // Valid
console.log(user.email); // Output: [email protected]
// This would throw an error:
// user.email = "invalid-email";
2. Computed Properties
Getters can calculate values on-demand:
const product = {
name: 'Laptop',
price: 999,
discount: 0.1,
get discountedPrice() {
return this.price * (1 - this.discount);
}
};
console.log(product.discountedPrice); // Output: 899.1
3. Unit Conversion
Getters and setters can handle unit conversions automatically:
const temperature = {
_celsius: 0,
get celsius() {
return this._celsius;
},
set celsius(value) {
this._celsius = value;
},
get fahrenheit() {
return (this._celsius * 9/5) + 32;
},
set fahrenheit(value) {
this._celsius = (value - 32) * 5/9;
}
};
temperature.celsius = 25;
console.log(temperature.fahrenheit); // Output: 77
temperature.fahrenheit = 68;
console.log(temperature.celsius); // Output: 20
4. In Classes
Getters and setters are commonly used in JavaScript classes:
class Person {
constructor(firstName, lastName, age) {
this._firstName = firstName;
this._lastName = lastName;
this._age = age;
}
get firstName() {
return this._firstName;
}
set firstName(value) {
if (typeof value !== 'string') {
throw new Error('First name must be a string');
}
this._firstName = value;
}
get lastName() {
return this._lastName;
}
set lastName(value) {
if (typeof value !== 'string') {
throw new Error('Last name must be a string');
}
this._lastName = value;
}
get fullName() {
return `${this._firstName} ${this._lastName}`;
}
get age() {
return this._age;
}
set age(value) {
if (value < 0 || value > 120) {
throw new Error('Age must be between 0 and 120');
}
this._age = value;
}
}
const john = new Person('John', 'Doe', 30);
console.log(john.fullName); // Output: John Doe
john.firstName = 'Jane';
console.log(john.fullName); // Output: Jane Doe
// This would throw an error:
// john.age = 150;
Best Practices for Getters and Setters
-
Keep them simple: Getters and setters should be fast and not have side effects that users wouldn't expect
-
Use the underscore convention for "private" backing properties (e.g.,
_propertyName
) -
Always include validation in setters when appropriate
-
Consider providing both getter and setter for a property, unless you want it to be read-only
-
Avoid circular references in getters that might cause infinite loops
-
Don't perform expensive operations in getters if they'll be called frequently
When to Use Getters and Setters
- When you need to validate data before assignment
- When a property requires computation
- When property access needs to trigger side effects
- When implementing data encapsulation
- When you need backward compatibility after refactoring
Summary
Getters and setters are powerful JavaScript features that provide control over property access in objects. They allow you to:
- Execute code when properties are accessed or modified
- Validate property values
- Create computed properties
- Transform data during assignment or retrieval
- Implement proper encapsulation
By using getters and setters appropriately, you can create more robust and maintainable JavaScript objects with controlled access to their data.
Exercises
-
Create an object representing a bank account with properties for balance and owner. Add getters and setters that prevent the balance from going negative.
-
Create a
Rectangle
class with width and height properties. Add getters and setters for both properties that ensure they are positive numbers. Add a getter for area that computes the area on-the-fly. -
Create an object that stores temperatures in Celsius but has getters and setters for Celsius, Fahrenheit, and Kelvin.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)