Learn how to use JavaScript getters and setters to manage internal data safely, ensuring encapsulation and data privacy in object-oriented programming.
In the world of object-oriented programming (OOP), encapsulation is a fundamental concept that allows us to bundle data and methods that operate on that data within a single unit, or object. This not only helps in organizing code but also in protecting the internal state of an object from unintended interference. One of the key tools in JavaScript for achieving encapsulation is the use of getters and setters. In this section, we will explore how these accessor properties work, their syntax, and how they contribute to safer and more maintainable code.
Getters and setters are special methods in JavaScript that allow you to define how to access and modify the properties of an object. They provide a way to control the access to an object’s properties, enabling you to add logic that runs when a property is accessed or modified.
By using getters and setters, you can:
Getters and setters can be defined within object literals or classes. Let’s go through the syntax for both.
In object literals, getters and setters are defined using the get
and set
keywords followed by a method name.
const person = {
firstName: 'John',
lastName: 'Doe',
// Getter for fullName
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
// Setter for fullName
set fullName(name) {
const parts = name.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
}
};
console.log(person.fullName); // Output: John Doe
person.fullName = 'Jane Smith';
console.log(person.firstName); // Output: Jane
console.log(person.lastName); // Output: Smith
In classes, getters and setters are defined similarly, but within the class body.
class Person {
constructor(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
}
// Getter for fullName
get fullName() {
return `${this._firstName} ${this._lastName}`;
}
// Setter for fullName
set fullName(name) {
const parts = name.split(' ');
this._firstName = parts[0];
this._lastName = parts[1];
}
}
const person = new Person('John', 'Doe');
console.log(person.fullName); // Output: John Doe
person.fullName = 'Jane Smith';
console.log(person._firstName); // Output: Jane
console.log(person._lastName); // Output: Smith
One of the primary uses of setters is to validate data before it is assigned to a property. This ensures that your object maintains a valid state.
class BankAccount {
constructor(balance) {
this._balance = balance;
}
// Getter for balance
get balance() {
return this._balance;
}
// Setter for balance with validation
set balance(amount) {
if (amount < 0) {
console.error('Balance cannot be negative.');
} else {
this._balance = amount;
}
}
}
const account = new BankAccount(100);
console.log(account.balance); // Output: 100
account.balance = 150;
console.log(account.balance); // Output: 150
account.balance = -50; // Output: Balance cannot be negative.
Getters can be used to create computed properties, which are properties whose values are calculated on the fly.
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
// Getter for area
get area() {
return this.width * this.height;
}
}
const rect = new Rectangle(5, 10);
console.log(rect.area); // Output: 50
Getters and setters can also be used to log access to properties, which can be useful for debugging or monitoring.
class Product {
constructor(name, price) {
this._name = name;
this._price = price;
}
// Getter for price with logging
get price() {
console.log(`Getting price: ${this._price}`);
return this._price;
}
// Setter for price with logging
set price(value) {
console.log(`Setting price to: ${value}`);
this._price = value;
}
}
const product = new Product('Laptop', 1000);
console.log(product.price); // Output: Getting price: 1000
product.price = 1200; // Output: Setting price to: 1200
Encapsulation is about keeping the internal state of an object hidden from the outside world and only exposing a controlled interface. Getters and setters are a perfect fit for this purpose as they allow you to control how properties are accessed and modified.
class User {
constructor(username, password) {
this._username = username;
this._password = password; // Private data
}
// Getter for username
get username() {
return this._username;
}
// Setter for password with validation
set password(newPassword) {
if (newPassword.length < 6) {
console.error('Password must be at least 6 characters long.');
} else {
this._password = newPassword;
}
}
}
const user = new User('john_doe', '123456');
console.log(user.username); // Output: john_doe
user.password = '123'; // Output: Password must be at least 6 characters long.
user.password = 'abcdef';
To make the most of getters and setters, consider the following best practices:
While getters and setters are powerful tools, they can introduce performance overhead if not used carefully. Each time a getter or setter is called, it executes a function, which can add up if used excessively in performance-critical code.
To mitigate performance issues:
Now that we’ve covered the basics of getters and setters, let’s encourage you to experiment with the code examples provided. Try modifying the examples to:
Let’s visualize how getters and setters interact with an object’s properties using a simple flowchart.
flowchart TD A[Access Property] --> B{Is it a Getter?} B -- Yes --> C[Execute Getter Function] B -- No --> D{Is it a Setter?} D -- Yes --> E[Execute Setter Function] D -- No --> F[Access Property Directly]
This diagram illustrates the decision-making process when accessing or modifying a property. If a getter or setter is defined, the corresponding function is executed; otherwise, the property is accessed directly.
Before we conclude, let’s summarize the key takeaways:
Remember, this is just the beginning. As you progress, you’ll build more complex and interactive web pages. Keep experimenting, stay curious, and enjoy the journey!