Since ES2020, we finally have truly private class features in JavaScript. While available in all modern browsers, this only includes very recent versions at the time of writing. For example, Chrome is currently on version 97; some private class features only became available in version 91.

Private class features

Regardless, this is the sort of thing that private class features let you do:

export default class Car {
#make;

constructor() {
this.#make = 'Toyota';
}

get make() {
return this.#make;
}
}

The #make instance field is truly private. It cannot be accessed outside the class. It's accessible only through the getter method, make(), which returns its value:

import Car from './car.js';

const car = new Car();

console.log(car.#make); // SyntaxError: Private field '#make' must be declared in an enclosing class
console.log(car.make); // 'Toyota'

WeakMaps

ES module

Here's how we might do that with better browser support. The following implementation, which uses a WeakMap, is supported from Chrome version 61:

const privateData = new WeakMap();

class Car {
constructor() {
privateData.set(this, {
make: 'Toyota'
});
}

get make() {
return privateData.get(this).make;
}
}

export default Car;

The private data is only accessible from the WeakMap. Because we don't export the WeakMap from the module, users of the class cannot access the private data:

import Car from './car.js';

const car = new Car();

console.log(privateData.make); // ReferenceError: privateData is not defined
console.log(car.make); // 'Toyota'

Constructor pattern

If you don't mind using the constructor pattern instead of an ES module, you can push support back even further—all the way to Chrome version 49 and above!

const Car = (function() {

'use strict';

const privateData = new WeakMap();

class Car {
constructor() {
privateData.set(this, {
make: 'Toyota'
});
}

get make() {
return privateData.get(this).make;
}
}

return Car;

})();

References: