JAVASCRIPT ENCAPSULATION

One of the most important concepts of object-oriented JS programming is encapsulation, or the delimitation of internal interfaces from the external ones. This practice is a “must” in the development of any application that is more complex than “hello world”. To understand it, let’s step out of the development world and look to the real world. Usually, the devices we use are quite complex. Being able to delimit their internal interface from the external one allows us to use them without major problems.

AN EXAMPLE FROM THE REAL WORLD

Let’s take the example of a coffee machine. Simple on the outside: a button, a display, a couple of holes … And, of course, the result: an excellent coffee! :). There are many details. But we still manage to use it even without knowing them. Coffee machines are pretty reliable, right? They can last for years, and only in case something stops working, we take them for repair. The secret behind the reliability and simplicity of a coffee machine is that all the details are optimized and hidden. If we removed the cover of the coffee machine, then its use would be much more complex (where should we press?), And dangerous (we could get an electric shock). As we will see later, objects are like coffee machines in programming. But in order to hide their internal details, we will not use a security cover, but rather a special language syntax and some conventions.

INTERNAL AND EXTERNAL INTERFACE

In object-oriented programming, properties and methods are divided into two groups:

  • Internal interface – methods and properties, accessible from the other methods of the class, but not from the outside.
  • External interface – methods and properties, also accessible from outside the class.

Continuing with the analogy of the coffee machine, what is hidden internally (a pump, a heating mechanism and so on) is its internal interface. The internal interface is used to make the object work, its elements interact with each other. For example, the pump is connected to the heating mechanism. But seen from the outside, the coffee machine is protected by a cover, so that no one can access it. The details are hidden and inaccessible, but we can take advantage of its features through its external interface. So, all we need to use an object is its external interface. We may be completely unaware of its internal workings, and that would be fine.

This was a general introduction.

In JavaScript, there are two types of fields for an object (properties and methods):

Public: accessible everywhere. These define the external interface. Until now we have always used public properties and methods.
Private: accessible only from within the classroom. These define the internal interface.

In many other programming languages ​​there is also the concept of a “protected” field: accessible only from within the class and from those that extend it (such as private fields, but in addition they are also accessible by inheriting classes). These are equally useful for defining the internal interface. They are generally more common than private fields, as our intention is usually to make them accessible in subclasses as well. Protected fields are not implemented in JavaScript at the language level, but in practice they are very useful, which is why they are often emulated. We will now build a coffee machine in JavaScript, with all kinds of properties described. A coffee machine is made up of many details; we will not model it in its entirety (although we could), in order to keep the example simple.

PROTECTED FIELDS

Let’s try to modify the waterAmount property making it protected, in order to have more control over it. For example, we don’t want anyone to be able to set it to a negative value. Protected properties are usually prefixed with an underscore _. This is not a language forcing, but rather a common convention among programmers, which specifies that these properties and methods should not be accessible from the outside. So our property becomes _waterAmount:

class CoffeeMachine {

  _waterAmount 0;

  set waterAmount(value) {

    if (value < 0) {

      value = 0;

    }

    this._waterAmount value;

  }

  get waterAmount() {

    return this._waterAmount;

  }

  constructor(power) {

    this._power power;

  }

}

// creiamo la macchina del caffè

let coffeeMachine new CoffeeMachine(100);

// aggiungiamo acqua

coffeeMachine.waterAmount = -10// _waterAmount diventerà 0, non -10

Access is now under control, so it is no longer possible to set the amount of water to a negative value.

PROPERTY READ-ONLY

Let’s try making the power property read-only. In some cases, we may need to define a property under construction, and not want to change it later. This is exactly the case for a coffee machine: the power cannot vary. To do this, we can simply define a getter, and no setter:

class CoffeeMachine {

  // …

  constructor(power) {

    this._power power;

  }

  get power() {

    return this._power;

  }

}

// creiamo la macchina del caffè

let coffeeMachine = new CoffeeMachine(100);

alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W

coffeeMachine.power = 25; // Errore (nessun setter)

PROTECTED FIELDS ARE INHERITED

If we inherit class MegaMachine extends CoffeeMachine, then nothing will prevent us from accessing this._waterAmount or this._power from the methods in the new class. Hence, the protected methods are inherited. Unlike the private ones, which we will see shortly.

PRIVATE FIELDS

Private fields should be preceded by #. These will only be accessible from within the classroom. For example, here we have a private property #waterLimit and a private method for controlling the water level #fixWaterAmount:

class CoffeeMachine {

  #waterLimit = 200;

  #fixWaterAmount(value) {

    if (value < 0) return 0;

    if (value this.#waterLimitreturn this.#waterLimit;

  }

  setWaterAmount(value) {

    this.#waterLimit = this.#fixWaterAmount(value);

  }

}

let coffeeMachine new CoffeeMachine();

// non possiamo accedere ai metodi privati dall’esterno della classe

coffeeMachine.#fixWaterAmount(123); // Errore

coffeeMachine.#waterLimit = 1000; // Errore

Language-wise, # is a special character to indicate that that field is private. We cannot therefore access it from the outside or from a sub-class. Furthermore, private fields do not conflict with public ones. We can have both a private #waterAmount and a public waterAmount field. For example, let’s make waterAmount a property to access #waterAmount:

class CoffeeMachine {

  #waterAmount = 0;

  get waterAmount() {

    return this.#waterAmount;

  }

  set waterAmount(value) {

    if (value < 0value = 0;

    this.#waterAmount = value;

  }

}

let machine new CoffeeMachine();

machine.waterAmount 100;

alert(machine.#waterAmount); // Errore

Unlike protected fields, private fields are provided by the language itself. And that’s a good thing. In case we were inheriting from CoffeeMachine then we would not have direct access to #waterAmount. We should rely on the waterAmount getter / setter. In many cases, such a limitation is too severe. If we extend a CoffeeMachine, we may rightfully want to access its internal fields. This is why protected fields are used more often, even if they are not really supported by the language syntax.

Copy to Clipboard

DEEPENING AI

Encapsulation in JavaScript is a fundamental concept in object-oriented programming (OOP) of restricting direct access to certain properties or methods of an object so that they can be modified only through a controlled interface. The main purpose is to ensure that an object’s internal data is protected from accidental or unintentional modification, thereby improving the security and maintainability of the code.

How to implement encapsulation in JavaScript.

1. Private variables and functions: Prior to ECMAScript 2015 (ES6), JavaScript did not have a native way to create private variables. However, encapsulation can be achieved using closures and constructor functions.

Example with closures:

function Persona(nome) {
     let _nome = nome; // Variabile privata

     this.getNome = function() {
        return _nome;
     };

     this.setNome = function(nuovoNome) {
       _nome = nuovoNome;
     };
}

const persona = new Persona(“Mario“);
console.log(persona.getNome()); // Mario
persona.setNome(“Luigi“);
console.log(persona.getNome()); // Luigi

In this example, the variable _nome is not directly accessible from the outside, but it can be read or modified via the public methods getNome and setNome. This type of encapsulation is achieved by the fact that Persona’s internal functions have access to the _nome variable, but it is not directly exposed.

2. Classes with ECMAScript 6 (ES6): With the introduction of classes in ES6, we can simulate encapsulation by using weakly private variables (using a convention such as the _ prefix to indicate that a property is private).

Example with classes (simulation):

class Persona {
       constructor(nome) {
          this._nome = nome; // Convenzione per variabili “private”
       }

       getNome() {
          return this._nome;
       }

       setNome(nuovoNome) {
          this._nome = nuovoNome;
       }
}

const persona = new Persona(“Mario“);
console.log(persona.getNome()); // Mario
persona.setNome(“Luigi“);
console.log(persona.getNome()); // Luigi

In this case, the variable _nome is treated as “private” by convention, even though it is technically accessible from the outside.

3. Private properties with ECMAScript 2019 (ES10): As of ECMAScript 2019, JavaScript has introduced native support for private properties using the # prefix.

Example with private properties:

class Persona {
       #nome; // Variabile privata

       constructor(nome) {
          this.#nome = nome;
       }

       getNome() {
          return this.#nome;
      }

      setNome(nuovoNome) {
          this.#nome = nuovoNome;
      }
}

const persona = new Persona(“Mario“);
console.log(persona.getNome()); // Mario
persona.setNome(“Luigi“);
console.log(persona.getNome()); // Luigi

// console.log(persona.#nome); // Errore: proprietà privata non accessibile

In this example, the #nome property is completely private and cannot be accessed or modified from the outside except through the defined public methods.

Advantages of encapsulation

1. Data protection: Reduces the risk of unintended or unwanted changes to the object’s internal data.

2. Modularity: Each object manages its own data and behaviors independently, making it easier to understand and maintain the code.

3. Flexibility: You can change the internal implementation of an object without changing the external code that uses it, as long as the public interface remains the same.

Examples of practical use

Encapsulation is often used to:

-Limit access to sensitive data.

-Prevent direct access to implementation details that should not be exposed.

-Create a stable interface for objects that can be used by developers without knowing the internal details.

Conclusion

Encapsulation in JavaScript is a fundamental concept for creating robust and modular code. With closure techniques, classes introduced in ES6, and native private properties as of ES10, encapsulation can be implemented in a variety of ways, depending on the needs of the project and compatibility with versions of JavaScript.

THE JAVASCRIPT LANGUAGE

THE JAVASCRIPT LANGUAGE

LINK TO THE CODE ON GITHUB

GITHUB