One feature missing in ES2015 (formerly ES6) classes is the concept of a private variable or function. However, thanks to ES2015 modules we can actually easily define private properties and functions that can only be accessed within our class.
Private Properties in ES2015 classes
Using a WeakMap which we have discussed before, we are going to implement the concept of private class properties.
Because classes are just objects, we can use WeakMaps on them. Then by specifying getter and or setter methods on the class, we can control access to these private properties. The beautiful thing about this is that using the export
keyword, we are only exposing the class Person. This is akin to the revealing module pattern where we choose what is exposed to the client.
Using something like an ES2016 Decorator, we can probably easily create the concept of a decorator which will allow us to define variables as private and even control access to them. Just some food for thought.
const privateProps = new WeakMap();
export class Person {
constructor(name, dob) {
privateProps(this, {name: name, dob: dob});
}
get name() {
return privateProps.get(this).name;
}
get dob() {
return privateProps.get(this).dob;
}
}
Private Methods
We are going to be borrowing the above example and introducing the concept of a private method. The goal of our private method is to only be used within the class itself, but not available to the client by any other means.
Traditionally the revealing module pattern allowed us to selectively choose what is exposed, we can achieve the same thing using ES2015 modules (like above).
const privateProps = new WeakMap();
// Private function for lowercasing a string
function _convertToLowercase(val) {
return val.toLowerCase();
}
export class Person {
constructor(name, dob) {
privateProps(this, {name: name, dob: dob});
}
get name() {
return _convertToLowercase(privateProps.get(this).name);
}
get dob() {
return privateProps.get(this).dob;
}
}
In our example above we create a rather redundant private method for lowercasing a string. The function _convertToLowercase
is only accessible from within the class itself. Because we are exporting the class, it doesn’t get exposed. This allows us to have the concept of a private method very easily.
I realise the private method is not on the class itself and that would be the ideal situation to be in. But given how classes actually operate, any method you currently define in the class gets added to the prototype of your class so it can be called from anywhere.
Conclusion
While ES2015 Javascript lacks the concept of visibility keywords, we can still achieve the same thing without them. This is why I really like Javascript, it doesn’t give you one way or the highway, but lets you implement hacks creative solutions to problems.
Supersets like TypeScript while they have concepts of visibility keywords, they’re not for the resulting output but only for the compiling stage where your code is checked. It will stop you if you are trying to illegally reference a private property or method.
Uhhhh…I think you haven’t heard of ES6 symbols.
“`
let _name = Symbol();
let _dob = Symbol();
export default class Person {
constructor(name, dob) {
this[_name] = name;
this[_dob] = dob;
}
get name() {
return _convertToLowercase(this[_name]);
}
get dob() {
return this[_dob];
}
}
“`
Only code that has access to `_name` and `_dob` can access those properties of your class instances.
The polyfills for both Symbols and WeakMap end up just sticking the private properties inside a hidden object hierarchy on your object anyway 🙂
Sometimes I think this is all a step backward from old-school JS anyway:
“`
function Person(name, dob) {
return {
name: function() {
return name;
}
dob: function() {
return dob;
}
}
}
“`
Symbols are unique (like gensym), but not private since they are exposed via reflection features like Object.getOwnPropertySymbols.
privateProps(this, {name: name, dob: dob}); -> privateProps.set(this, {name: name, dob: dob});
If you use this solution then you have to use the ugly syntax: “privateProps.get(this).property” every time you want to read or write a private property.
On the other hand, you could without using any external collection achieve using this.property for reading both public and private variables as well as for writing private variables. However, you would need to use this._.property for changing public properties from within methods. Luckily it is far less likely that mutator methods change public variables (if ever). Of course for anyone using your object they just use the normal properties and they won’t be able to see the private properties.
I wrote a trivial example here:
https://gist.github.com/fedler/191bbc9640c2e91e1f5e493928ae9561
I went ahead and created a gist to show how.
They should just allow use of ‘let’ in Class scope:
export class MyClass {
myPubVar = {};
let myPrivateVar = {};
constructor() {
}
let myPrivateFunc = () => {
};
}
This seems an obvious solution to me, yet unfortunately it is invalid syntax.