Understanding Private Variables in JavaScript Class Constructors

Understanding Private Variables in JavaScript Class Constructors

This week, my class has been looking into factory functions and classes. Naturally, the topic of 'private variables' came up, and has been a source of much confusion. To me, this confusion is similar to the confusion over Closure, which I addressed in a recent blog post: we use them instinctively, they make total sense to us, but when they're given a name we suddenly feel confused and wonder if we don't really get it after all.

So if, like me, you hear the phrase 'private variable' and you begin to sweat, then let me help clarify what we're talking about and why you probably already use them.

Let's set the scene

Let's create a simple User class in JavaScript which will form the basis for our examples:

class User {
   constructor(name, age, favNumber) {
      return {name, age, favNumber}
   }
}

let firstUser = new User("Anna", 33, 7)
console.log(firstUser.name) // logs "Anna"

OK, so we have a super basic class which returns the parameters as properties of the object we create.

Side note: Returning parameters within the constructor

You may have noticed in the third line above, that I opted to simply return the parameters in the constructor. This is the same thing as:

this.name = name
this.age = age
this.favNumber = favNumber

These return values also come in handy later, and for me help me visualise what I'm making 'public' vs what I'm making 'private'. It also helps save some typing, and I'm always in favour of lazin-- uhh, efficiency.

What is public? What is private?

In the above code, all of our parameters are 'public'. Now, don't let the standard ideas of public and private confuse you: we are referring here to the global scope. We can type into the global scope firstUser.favNumber and we will get 7, because favNumber is being returned. In this instance, name, age, and favNumber are all public.

So let's create a private variable, a super secret magic number using our super secret Formula For Success™.

class User {
  constructor(name, age, favNumber) {
    let superSecretNumber = Math.round((age * favNumber) / name.length);
    function logOutcome() {
      console.log(`Wow! Your secret number is ${superSecretNumber}`);
    }
    return { name, age, favNumber, logOutcome };
  }
}

let firstUser = new User("Anna", 33, 7);
console.log(firstUser.name); // logs "Anna"
console.log(firstUser.superSecretNumber); // WHOMP WHOMP: 'undefined'
firstUser.logOutcome();  // logs "Wow! Your secret number is 58"

Voila! That's a private variable in a JavaScript class: superSecretNumber, which we cannot access from the global scope. Trying to log firstUser.superSecretNumber gives us undefined. However, we can also use a Closure (again, read my blog post if this is shaky ground for you) in the form of the logOutcome function, which accesses this private variable and uses it in some way (in this case, by logging it with a message). If you imagine a much more data-sensitive algorithm, you can see how protecting variables in this way is extremely important.

superSecretNumber is not accessible anywhere else in your code, and if you log your object in the browser or elsewhere, you won't see superSecretNumber anywhere in the list of properties. It is hidden from 'public' view. Of course, it is still public in the sense that if someone downloaded your code and read through it, they would see you defining the variable and how you did it: this is why it's important not to take your usual/normal understanding of public and private in this instance.

One last convention worth noting

Take it or leave it, but it is convention to use an underscore _ in front of private variables. So in the above example we would name superSecretNumber as _superSecretNumber instead. There are some libraries that work with this convention so it's worth noting and building into your habits.