Using WeakMap for Private Properties
This blog is about a cool idea of leveraging WeakMap to have private properties in Javascript Classes. A private property is a property that is only accessible to member functions of instances of the same class. Javascript inherently does not support private properties unlike languages such as Java or C++. It is however, at the time of writing, at Stage 0 Proposal to add it to the ECMAScript spec.
As a recap from my previous blog, a WeakMap is:
- iterable by providing keys only
- not enumerable
- unique object or function references
- does not accept primitive data types as keys
const myWeakMap = new WeakMap();
const harry = {};
const potter = () => {};
myWeakMap.set(harry, "cat");
myWeakMap.set(potter, 7);
myWeakMap.has(harry); // true
myWeakMap.get(potter); // 7
myWeakMap.delete(harry);
WeakMap Technique #
Traditionally, the way to have private properties in Javascript is to either prefix your variables or to encapsulate in a closure. Both of these methods do work, however they are either not restrictive enough (prefixes) or too restrictive (closures).
A WeakMap is similar to a HashMap but it allows objects as keys as well as not having a strong reference to its values (that’s why it’s called weak).
In the example below, we pass in an empty object into the WeakMap to hold all of the private properties of the class Wizard
. To store the private properties, we also pass in the reference to the unique this
object of the instance of the class Wizard
as a key to the WeakMap.
const Wizard = (function() {
const _private = new WeakMap();
const internal = (key) => {
// Initialize if not created
if (!_private.has(key)) {
_private.set(key, {});
}
// Return private properties object
return _private.get(key);
};
class Wizard {
constructor(name, house) {
internal(this).name = name;
internal(this).house = house;
}
getName() {
return internal(this).name;
}
setName(name) {
internal(this).name = name;
}
getHouse() {
return internal(this).house;
}
setHouse(house) {
internal(this).house = house;
}
}
return Wizard;
}());
When we actually run this code, you can see on the second line that when we try to access the property _private
the class returned does not have reference to it because of the IIFE. However internal class methods can still change the values under _private
. So you can only change the properties within the Class by using its accessor methods such as get
and set
. In doing so, keeping the namespace hidden from all functions except members of the class effectively implements private properties.
const harrypotter = new Wizard('Harry Potter', 'Gryffindor');
console.log(harrypotter); // Wizard {}
console.log(harrypotter.getName()); // "Harry Potter"
harrypotter.setName('Arry Pottr');
console.log(harrypotter.getName()); // "Arry Pottr"
Sources: