How Encapsulation is Achieved in JavaScript

shivam bhatele
Quick Code
Published in
7 min readFeb 16, 2023

Introduction

JavaScript is a high-level, dynamic, and interpreted programming language popular for developing interactive and dynamic web applications. It is an object-oriented language that provides a flexible and expressive syntax for creating and manipulating objects.

Encapsulation in JavaScript refers to the practice of wrapping data and behavior within a single entity, known as an object and limiting external access to the object’s internal state. This helps to protect the data and behavior from external interference and misuse. In this article, we will explore different methods to achieve encapsulation in JavaScript.

What does Encapsulation Mean?

Encapsulation is a fundamental concept in object-oriented programming that refers to the process of grouping data together and behavior within a single entity called an object. The idea behind encapsulation is to hide the internal implementation details of an object and provide a well-defined interface for accessing and manipulating that object’s state.

Encapsulation helps to promote good software design by promoting the principles of information hiding, abstraction, and modularity.

  • Information hiding refers to the idea of hiding the internal state of an object from external access.
  • Abstraction refers to the process of hiding the implementation details of an object and exposing only the relevant information to the outside world.
  • Modularity refers to the process of breaking down a complex system into smaller, more manageable parts, and encapsulated objects are used to achieve this.

Encapsulation provides several benefits to software development, including improved modularity, increased code maintainability, and reduced coupling between objects. Developers can ensure that the internal state of an object cannot be directly modified from outside of the object by encapsulating data and behavior within objects. This decreases the possibility of unintended consequences and makes it easier to manage code changes over time.

Encapsulation also makes it easier to understand the behavior of an object, since all of its data and methods are packaged together within a single unit. This can lead to improved code readability and reduced development time, making encapsulation a valuable tool for improving software development.

What are the different ways to achieve encapsulation in JavaScript?

JavaScript is a loosely typed language and does not have traditional access modifiers like private, protected, and public which are common in other object-oriented programming languages. However, we can achieve encapsulation in JavaScript through the following methods:

1. Closures:

Closures are inner functions that have access to the variables and functions in their outer scope. Closures can be used in JavaScript to create private properties and methods that are inaccessible from the outside. Encapsulation can be achieved by defining a function within an object and assigning the function to a property of the object.

Here is an example of encapsulation using closures in JavaScript:

const person = (function () {
let name = ‘John Doe’;
return {
getName: function () {
return name;
},
setName: function (newName) {
name = newName;
}
};
})();
console.log(person.getName()); // outputs ‘John Doe’
person.setName(‘Jane Doe’);
console.log(person.getName()); // outputs ‘Jane Doe’

In this example, the person object is created using an immediately-invoked function expression (IIFE). The name property is declared within the function and therefore is private and inaccessible from the outside. The getName and setName methods are added to the returned object and provide the only means of accessing and manipulating the private name property.

2. Constructor functions:

Constructor functions are functions that are used to create objects in JavaScript. To achieve encapsulation using constructor functions, you can declare private properties and methods within the constructor function and make them inaccessible from the outside.

Here is an example of encapsulation using constructor functions in JavaScript:

function Person(name) {
let _name = name;
this.getName = function () {
return _name;
};
this.setName = function (newName) {
_name = newName;
};
}
const person = new Person(‘John Doe’);
console.log(person.getName()); // outputs ‘John Doe’
person.setName(‘Jane Doe’);
console.log(person.getName()); // outputs ‘Jane Doe’

In this example, the Person constructor function is used to create the person object. The _name property is declared within the constructor function and therefore is private and inaccessible from the outside. The getName and setName methods are added to the Person.prototype object and provide the only means of accessing and manipulating the private _name property.

3. Class syntax:

The ECMAScript 6 standard introduced class syntax to JavaScript, which provides a more traditional object-oriented approach to encapsulation. The class syntax supports the creation of private properties and methods with the # symbol, although this is a proposed feature that may not be widely supported yet.

Here is an example of encapsulation using class syntax in JavaScript:

class Person {
#name;
constructor(name) {
this.#name = name;
}
getName() {
return this.#name;
}
setName(newName) {
this.#name = newName;
}
}
const person = new Person(‘John Doe’);
console.log(person.getName()); // outputs ‘John Doe’
person.setName(‘Jane Doe’);
console.log(person.getName()); // outputs ‘Jane Doe’

In this example, the Person class creates the person object. The #name property is declared within the class and therefore is private and inaccessible from the outside. The getName and setName methods are added to the Person.prototype object and provide the only means of accessing and manipulating the private #name property.

4. WeakMap:

A WeakMap is a collection of key-value pairs in JavaScript, where the keys are objects and the values can be of any type. One of the benefits of using WeakMap is that it allows for the creation of private properties and methods. To achieve encapsulation using WeakMap, you can create a WeakMap and use objects as keys to store private properties and methods.

Here is an example of encapsulation using WeakMap in JavaScript:

const _name = new WeakMap();
class Person {
constructor(name) {
_name.set(this, name);
}
getName() {
return _name.get(this);
}
setName(newName) {
_name.set(this, newName);
}
}
const person = new Person(‘John Doe’);
console.log(person.getName()); // outputs ‘John Doe’
person.setName(‘Jane Doe’);
console.log(person.getName()); // outputs ‘Jane Doe’

In this example, the _name WeakMap is used to store the private name property. The Person class is used to create the person object. The getName and setName methods are added to the Person.prototype object to access and alter the private name property using the WeakMap.

Points to keep in mind for achieving effective encapsulation in JavaScript

Using good coding practices is essential for achieving effective encapsulation in JavaScript. Effective encapsulation ensures that the code is organized, maintainable, and reusable and that the data and behavior of an object are protected from external interference.

Here are a few good coding practices that can help you achieve effective encapsulation in JavaScript:

1. Declare private properties and methods:

Private properties and methods can be declared within a class or using a WeakMap. This helps to protect the internal state of an object from external interference. Private properties and methods are not accessible from the outside and can only be accessed through the public interface of the object.

class Person {
#age = 0;
incrementAge() {
this.#age += 1;
}
}
const person = new Person();
console.log(person.#age); // Uncaught SyntaxError: The private field ‘#age’ must be specified in an enclosing class

In this example, the private property “age” and the private method “incrementAge” can only be accessed within the class, and not from the outside.

2. Use getters and setters:

Private properties should be accessed and modified using getters and setters. This helps to ensure that the internal state of an object is maintained and that any necessary validation or error handling can be implemented.

For example, you can declare a getter and setter for the private property “age” in the following way:

class Person {
#age = 0;
get age() {
return this.#age;
}
set age(value) {
if (value >= 0) {
this.#age = value;
}
}
}
const person = new Person();
person.age = 25;
console.log(person.age); // 25

In this example, the getter “age” returns the value of the private property “age”, and the setter “age” sets the value of the private property “age” while ensuring that the value is greater than or equal to zero.

3. Keep the constructor simple:

The constructor should only be used to initialize the object’s properties and should not contain any complex logic or processing. This helps to keep the constructor simple and easy to understand as well as making the code easier to maintain and update.

For example, the following code initializes the properties of a class within the constructor:

class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const person = new Person(“John Doe”, 25);
console.log(person.name); // John Doe
console.log(person.age); // 25

In this example, the constructor initializes the properties “name” and “age” of the object, while keeping the logic simple and easy to understand.

4. Minimize coupling between objects:

Objects should be designed to be self-contained and should have minimal dependency on other objects. This helps to ensure that changes to one object do not have unintended consequences on other objects.

For example, consider the following code, where the class “Person” depends on the class “Address”:

class Address {
constructor(street, city) {
this.street = street;
this.city = city;
}
}
class Person {
constructor(name, age, address) {
this.name = name;
this.age = age;
this.address = address;
}
}

const address = new Address(“123 Main St”, “San Francisco”);

const person = new Person(“John Doe”, 25, address); console.log(person.address.street); // 123 Main St console.log(person.address.city); // San Francisco

In this example, the class “Person” has a direct dependency on the class “Address”, which means that changes to the “Address” class may have unintended consequences on the “Person” class. To minimize coupling, the “Person” class could store a reference to the “Address” object, rather than including it as a property within the class.

5. Use inheritance sparingly:

Inheritance can make it more difficult to maintain and update your code. Consider using composition instead of inheritance when possible.

Conclusion

  • Encapsulation is an important concept in software engineering that helps to promote modularity, abstraction, and information hiding in code.
  • JavaScript supports encapsulation through various techniques, including closures, constructor functions, class syntax, and WeakMap.
  • Class syntax is one of the most commonly used approaches for encapsulation in JavaScript, allowing developers to define objects with encapsulated properties and methods within the class.
  • WeakMap is another mechanism for encapsulation in JavaScript, allowing developers to store private data on an object that can only be accessed through methods within the object.
  • Using good coding practices, such as minimizing coupling and maximizing cohesion, is essential for achieving effective encapsulation in JavaScript.

--

--

shivam bhatele
Quick Code

I am a Software Developer and I loved to share programming knowledge and interact with new people. Also I am big lover of dogs, reading, and dancing.