Javascript

Private Properties and Methods in JavaScript Classes

In this post you’ll learn all about private properties and methods in JavaScript using the new # syntax. The Public and Private Instance Fields Proposal is...

Written by Luci · 4 min read >

In this post you’ll learn all about private properties and methods in JavaScript using the new # syntax.

The Public and Private Instance Fields Proposal is currently at Stage 3 – close to completion. Also check out the class field examples on TC39’s GitHub.

This proposal introduces a brand new syntax that finally gives us private properties and methods in JavaScript. As the name suggests, we’ll be looking at instance properties and methods – meaning objects created using the new keyword.

Before this syntax was introduced, JavaScript didn’t have (and actually, still doesn’t have unless we adopt Babel) true private properties and methods.

This lack of feature led to emulating private properties and methods by using an underscore-prefix:

function User(name) {
  this._id = 'xyz';
  this.name = name;
}

User.prototype.getUserId = function () {
  return this._id;
}

User.prototype._destroy = function () {
  this._id = null;
};

const user = new User('Mike Watson');
user._id; // xyz
user.getUserId(); // xyz
user._destroy();
user.getUserId(); // null

Even though this._id and User.prototype._destroy were intended to be private, this prefixing doesn’t stop anyone from accessing any of the properties as they are part of the User object.

Above we’re calling user._destroy() when really it is considered private and could change at any time, so users should not use or rely on our private properties or methods.

Now with the introduction of the class keyword, we’ve now arrived at a Stage 3 proposal – we’re almost there! So what does it look like?

Let’s switch our .prototype approach over to a class and go from there.

Constructor Functions to Classes

With the introduction of the class keyword, fast-forwarding to today gives us an equivalent of the previous .prototype example:

class User {
  constructor(name) {
    this._id = 'xyz';
    this.name = name;
  }
  getUserId() {
    return this._id;
  }
  _destroy() {
    this._id = null;
  }
}

const user = new User('Mike Watson');
user._id; // xyz
user.getUserId(); // xyz
user._destroy();
user.getUserId(); // null

But the problem still remains. However, this new feature is only available with a class, hence the switch.

📣 Note: class is syntax sugar and is not something fundamentally different from prototypes. A class in most cases is compiled down to ES5 constructor functions and properties and methods are translated onto the prototype!

Private Properties in Classes

Now we’ve got our class setup, let’s make the _id property private property using #:

class User {
  #id = 'xyz';
  constructor(name) {
    this.name = name;
  }
  getUserId() {
    return this.#id;
  }
}

Something is also different above, we’ve declared #id = 'xyz'; on one-line above the constructor! This is called property initilizer syntax and is the reason we’ll be using @babel/plugin-proposal-class-properties (I’ll show you how to set this stuff up at the end of this post too).

You could also do this and declare the private property as undefined and then assign inside the constructor:

class User {
  #id;
  constructor(name) {
    this.name = name;
    this.#id = 'xyz';
  }
  getUserId() {
    return this.#id;
  }
}

It’s required to declare the private property #id; you’re creating on the class itself otherwise you’ll get an error such as Private name #id is not defined.

We can only reference the #id property inside the class, any public property access would just be undefined:

const user = new User('Mike Watson');
user.id; // undefined
user.getUserId(); // xyz

Now we’ve grasped private properties, let’s move onto private methods!

Private Methods in Classes

First off, before we look at private methods, there’s a super easy way involving a private property and an arrow function (so we’re kinda cheating by calling it a method… but it looks and behaves like one):

class User {
  #id = 'xyz'
  constructor(name) {
    this.name = name;
  }
  getUserId() {
    return this.#id;
  }
  #destroy = () => {
    this.#id = null;
  };
}

As #destroy is actually a private property with an arrow function assigned, our setup would work out of the box. However, it would not be translated into a prototype method. Our this context is correct though, so you could totally just use arrow functions – but we lose the benefit of using the prototype and sharing the methods throughout multiple instances, instead with each new call they would be constructed again.

Really though, we want to do it the proper way and use a method, which would be transformed into User.prototype.destroy = function () {} if it wasn’t private, our getUserId() {} method would live on the prototype instead:

class User {
  #id = 'xyz';
  constructor(name) {
    this.name = name;
  }
  getUserId() {
    return this.#id;
  }
  #destroy() {
    this.#id = null;
  }
}

With ESLint this turned out to be a bit more of a headache than I’d anticipated, as running the code gave me this:

❌ error ‘destroy’ is not defined no-undef

I went through a rabbit-hole of GitHub issues (this and this) to arrive at a solution to using the new feature:

// eslint-disable-next-line no-undef
#destroy() {
  this._id = null;
}

Basically forcing the no-undef error to silence. You can then use this.#destroy() anywhere inside the class after this with no issues – the code compiles down perfectly, giving us this object:

User { name: 'Todd Motto', getUserId: ƒ }

As promised, I’ll show you how to set it up with Babel and you can even download the source project.

Using Private Properties and Methods with Babel

First we’ll need to consider the following:

Install them all:

npm install --save-dev @babel/plugin-proposal-class-properties @babel/plugin-proposal-private-methods babel-eslint

Once we’ve installed the above packages, we’ll need to change our .eslintrc.json to:

//.eslintrc.json
{
  "parser": "babel-eslint"
}

This uses Babel’s ESLint parser over ESLint directly to give us finer control.

Next, we then need a .babelrc that looks like:

// .babelrc
{
  "presets": ["@babel/preset-env"],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-proposal-private-methods"
  ]
}

You’re good to go, now you can go and use private properties and methods properly in JavaScript for the first time.

Summary

The way we write JavaScript can now give us true private properties and methods.

Through the Babel compiler webpack handles our modules under-the-hood to scope our JavaScript in a far better way than the this._id prefix approach – it hides the property, or method, altogether.

When the feature lands, we’ll have true private properties and methods in JavaScript, until then it’s compiling with Babel (which is a sensible approach anyway).

Written by Luci
I am a multidisciplinary designer and developer with a main focus on Digital Design and Branding, located in Cluj Napoca, Romania. Profile
0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x