JavaScript: Making class variables ‘private’

In this post, we shall look at a technique to make members of a JavaScript class private.

Background

Consider object-oriented languages like C++ and Java. Both languages have the concept of access specifiers.

An access specifier is a special keyword that modifies or describes the level of accessibility of a variable in a class. The three access specifiers provided are public, protected, and private.

Public members are accessible anywhere in the program. We can access them from within the class, from other classes, or outside classes.
Protected members can be accessed only within the class they are defined in, and any of the subclasses of that class.
Private members are accessible only in the class they are defined in and nowhere else.

As we see, private class members provide the highest level of protection and safety for class and instance variables. We cannot modify private variables from the outside.

All class members are public in JavaScript

However, JavaScript doesn’t natively support private class members. Consider the following JavaScript code:

class SomeRandomClass
{
  constructor(attribute)
  {
    this.attribute=attribute;
  }
  display()
  {
    console.log(`Attribute is ${this.attribute}.`);
  }
}

const obj=new SomeRandomClass('attr1');
obj.display();

We have a class SomeRandomClass. We use the constructor to set the ‘attribute’ property of objects of this class. There is a ‘display’ function to display the ‘attribute’ property.

Our browser output is:

Attribute is [attr1].

However, we can also do this:

console.log(obj);
obj.attribute='Something else';
obj.display();

We get the following output in the browser:

how to make class variables 'private' in JavaScript

When we display our object, we see all its properties, including the ‘attribute’ property. Now that we know the property name, we change it. When ‘display’ is called again, the output reflects the changes we made to the object.

This is obviously a problem. Consider the following scenario:
Our class contains some complex logic and performs calculations for some critical tasks. Consequently, it is extremely important to restrict access to our class and instance variables. We want to prevent them from being modified, or indeed, accessed, from the outside.

We shall see how we can achieve this effect in this post.

Creating a JavaScript class with ‘private’ instance variables.

The core premise of private variables and methods is to make them accessible only to the class they are defined in.

Let us see how we can create this effect.

Let us create a timer class. Our timer object will increment the time by one second every time we call some method on it. Naturally, we want to keep the inner workings of our timer private.

For the sake of simplicity, we will be using the special syntax for classes provided in ECMAScript 2015 for this post.

Type in the following JavaScript code:

const myTimer=(function()
{
  const PRIVATE_DATA=new Map();
  class ClassWithPrivateData
  {
    constructor()
    {
      const count=()=>
      {
        const p=PRIVATE_DATA.get(this);
        ++p.seconds;
        if(p.seconds===60){++p.minutes;p.seconds=0;}
        if(p.minutes===60){++p.hours;p.minutes=0;}
      }
      const p={
        hours:0,
        minutes:0,
        seconds:0,
        count:count,
      };
      PRIVATE_DATA.set(this,p);
    }
    next(format)
    {
      const p=PRIVATE_DATA.get(this);
      p.count();
      return format.replace("%H",p.hours).replace("%m",p.minutes).replace("%s",p.seconds);
    }
  }
  return ClassWithPrivateData;
})();

There is quite a lot going on here, so let us examine this code bit by bit.

The basic structure followed.

The first thing to notice is that this code snippet follows the following structure:

const myClassName=(function()
{
  //Variables scoped within the function.
  class SomeClassName
  {
    //Class definition.
  }
  return SomeClassName;
})();

Here, we define an anonymous function within the parenthesis that defines our class and returns it. We immediately call that function and assign its return value to the constant ‘myClassName’. Consequently, we will refer to our class via ‘myClassName’.

Now inside our anonymous function, we have defined a constant Map object named ‘PRIVATE_DATA’. This variable is scoped within the function definition.

const PRIVATE_DATA=new Map();

‘PRIVATE_DATA’ will hold the private data of all objects of our class. As we shall see, it will hold key-value pairs where the objects are the keys, and their private data are the values.
Our ‘PRIVATE_DATA’ map is accessible inside the class also.

Due to the above design choice, we have to ensure that we get our private data from the map whenever we define our class and instance methods.

Now that we have got this far, let us examine the structure of our class constructor.

The structure of the constructor.

Our constructor definition can be broadly divided into three portions:

  • The definition of all functions we want ‘private’.
  • The definition of our object containing all private values.
  • Adding the above-mentioned object to the map containing all private data.

Let us look at these portions one by one.

Our ‘private’ methods.

In our timer class, we have one ‘private’ method to modify the time, i.e., increment it by one second. This method is defined as follows:

const count=()=>
{
  const p=PRIVATE_DATA.get(this);
  ++p.seconds;
  if(p.seconds===60){++p.minutes;p.seconds=0;}
  if(p.minutes===60){++p.hours;p.minutes=0;}
}

The first line of the function gets the private data of that instance from the ‘PRIVATE_DATA’ map and assigns it to the variable ‘p’. Note that the instance has other private attributes that are defined on ‘p’ like ‘seconds’, ‘minutes’, and ‘hours’.
The count function increments the time of the timer by one second.

The function is defined differently, though. Here, the arrow function syntax is used. This is because of the following reason:

  • Inside any function, the ‘this’ keyword refers to an instance of that function.
  • But in this case, we want ‘this’ to refer to the instance of our class and not to the instance of our function.
    Therefore, we use an arrow function.
  • An arrow function doesn’t have its own binding of ‘this’. Inside an arrow function, ‘this’ will refer to whatever it was referring to outside the function definition.
  • Inside our ‘count’ function, we want the reference pointed to by ‘this’ to be the same as the current instance. So we use an arrow function.

Due to the properties of arrow functions, all private functions defined like this will be arrow functions.

Object containing the private data.

After defining all our private methods, we define an object to contain all our private data members. This object is then added to the map of private data. The key of this map entry is the object itself.

const p={
  hours:0,
  minutes:0,
  seconds:0,
  count:count,
};
PRIVATE_DATA.set(this,p);

Our object contains private data members like the hours, minutes and the seconds of the timer. The ‘count’ function defined before is also present. At the end of the constructor, we set this object into the map containing the private data members. The key of the entry is the object itself, pointed to by ‘this’. This enables us to access our private data from any other class method using the current object as the key.

Finishing touches.

We now need a function that calls ‘count’ and returns the time in the format specified. We thus define the ‘next’ function:

next(format)
{
  const p=PRIVATE_DATA.get(this);
  p.count();
  return format.replace("%H",p.hours).replace("%m",p.minutes).replace("%s",p.seconds);
}

The first thing to do is to access all the private members. For that, we use the current object as the key and get our private data object from the map into the constant ‘p’.
The private ‘count’ method is called via ‘p’. We return the time of the timer in the format requested.

At the end, we return our class from the function.

This return value is captured by the ‘myTimer’ variable outside the function. We can now use our timer class which is now named ‘myTimer’.

Testing our class.

Let us test whatever we have written so far.

Execute the following code:

const timer=new myTimer();
console.log(timer);

Our browser output is:

how to make class variables 'private' in JavaScript

As we see, the private attributes and methods are not visible at all. Furthermore, we cannot access them directly. If we try to execute:

console.log(timer.seconds);

we get the value as undefined. No one can mess with the inner workings of our timer class.
We have thus successfully hidden the workings of our timer class from the outside.

Leave a Reply

Your email address will not be published. Required fields are marked *