Private properties in JavaScript ES6 classes
Is it possible to create private properties in ES6 classes?
Here's an example.
How can I prevent access to instance.property
?
class Something {
constructor(){
this.property = "test";
}
}
var instance = new Something();
console.log(instance.property); //=> "test"
Solution 1:
Short answer, no, there is no native support for private properties with ES6 classes.
But you could mimic that behaviour by not attaching the new properties to the object, but keeping them inside a class constructor, and use getters and setters to reach the hidden properties. Note that the getters and setters gets redefine on each new instance of the class.
ES6
class Person {
constructor(name) {
var _name = name
this.setName = function(name) { _name = name; }
this.getName = function() { return _name; }
}
}
ES5
function Person(name) {
var _name = name
this.setName = function(name) { _name = name; }
this.getName = function() { return _name; }
}
Solution 2:
Private class features is in Stage 3 proposal. The majority of its features are supported by all major browsers.
class Something {
#property;
constructor(){
this.#property = "test";
}
#privateMethod() {
return 'hello world';
}
getPrivateMessage() {
return this.#property;
}
}
const instance = new Something();
console.log(instance.property); //=> undefined
console.log(instance.privateMethod); //=> undefined
console.log(instance.getPrivateMessage()); //=> test
console.log(instance.#property); //=> Syntax error
Solution 3:
To expand on @loganfsmyth's answer:
The only truly private data in JavaScript is still scoped variables. You can't have private properties in the sense of properties accessed internally the same way as public properties, but you can use scoped variables to store private data.
Scoped variables
The approach here is to use the scope of the constructor function, which is private, to store private data. For methods to have access to this private data they must be created within the constructor as well, meaning you're recreating them with every instance. This is a performance and memory penalty, but some believe the penalty is acceptable. The penalty can be avoided for methods that do not need access to private data by adding them to the prototype as usual.
Example:
function Person(name) {
let age = 20; // this is private
this.name = name; // this is public
this.greet = function () {
// here we can access both name and age
console.log(`name: ${this.name}, age: ${age}`);
};
}
let joe = new Person('Joe');
joe.greet();
// here we can access name but not age
Scoped WeakMap
A WeakMap can be used to avoid the previous approach's performance and memory penalty. WeakMaps associate data with Objects (here, instances) in such a way that it can only be accessed using that WeakMap. So, we use the scoped variables method to create a private WeakMap, then use that WeakMap to retrieve private data associated with this
. This is faster than the scoped variables method because all your instances can share a single WeakMap, so you don't need to recreate methods just to make them access their own WeakMaps.
Example:
let Person = (function () {
let privateProps = new WeakMap();
class Person {
constructor(name) {
this.name = name; // this is public
privateProps.set(this, {age: 20}); // this is private
}
greet() {
// Here we can access both name and age
console.log(`name: ${this.name}, age: ${privateProps.get(this).age}`);
}
}
return Person;
})();
let joe = new Person('Joe');
joe.greet();
// here we can access joe's name but not age
This example uses an Object to use one WeakMap for multiple private properties; you could also use multiple WeakMaps and use them like age.set(this, 20)
, or write a small wrapper and use it another way, like privateProps.set(this, 'age', 0)
.
The privacy of this approach could theoretically be breached by tampering with the global WeakMap
object. That said, all JavaScript can be broken by mangled globals. Our code is already built on the assumption that this isn't happening.
(This method could also be done with Map
, but WeakMap
is better because Map
will create memory leaks unless you're very careful, and for this purpose the two aren't otherwise different.)
Half-Answer: Scoped Symbols
A Symbol is a type of primitive value that can serve as a property name. You can use the scoped variable method to create a private Symbol, then store private data at this[mySymbol]
.
The privacy of this method can be breached using Object.getOwnPropertySymbols
, but is somewhat awkward to do.
Example:
let Person = (function () {
let ageKey = Symbol();
class Person {
constructor(name) {
this.name = name; // this is public
this[ageKey] = 20; // this is intended to be private
}
greet() {
// Here we can access both name and age
console.log(`name: ${this.name}, age: ${this[ageKey]}`);
}
}
return Person;
})();
let joe = new Person('Joe');
joe.greet();
// Here we can access joe's name and, with a little effort, age. ageKey is
// not in scope, but we can obtain it by listing all Symbol properties on
// joe with `Object.getOwnPropertySymbols(joe)`.
Half-Answer: Underscores
The old default, just use a public property with an underscore prefix. Though not a private property in any way, this convention is prevalent enough that it does a good job communicating that readers should treat the property as private, which often gets the job done. In exchange for this lapse, we get an approach that's easier to read, easier to type, and faster.
Example:
class Person {
constructor(name) {
this.name = name; // this is public
this._age = 20; // this is intended to be private
}
greet() {
// Here we can access both name and age
console.log(`name: ${this.name}, age: ${this._age}`);
}
}
let joe = new Person('Joe');
joe.greet();
// Here we can access both joe's name and age. But we know we aren't
// supposed to access his age, which just might stop us.
Conclusion
As of ES2017, there's still no perfect way to do private properties. Various approaches have pros and cons. Scoped variables are truly private; scoped WeakMaps are very private and more practical than scoped variables; scoped Symbols are reasonably private and reasonably practical; underscores are often private enough and very practical.