How does JavaScript emulate private
members? Back
As we all know, the proposal of private
instance fields in classes has been pushed towards stage 4 since ES2022. It means that we can define private members inside classes like other OOP languages, which are limited to access outside a class in user code.
How does JavaScript emulate such behaviour? Use WeakMap
. For why? The MDN document has given us the answer:
-
Compared to a
Map
, aWeakMap
does not hold strong references to the object used as the key, so the metadata shares the same lifetime as the object itself, avoiding memory leaks. -
Compared to using non-enumerable and/or
Symbol
properties, aWeakMap
is external to the object and there is no way for user code to retrieve the metadata through reflective methods likeObject.getOwnPropertySymbols
.class A { constructor() { this[Symbol('#private')] = 'unreachable value?'; } } // we can access it actually: const a = new A(); a[Object.getOwnPropertySymbols(a)[0]]; // => "unreachable value?"
-
Compared to a closure, the same
WeakMap
can be reused for all instances created from a constructor, making it more memory-efficient, and allowing different instances of the same class to read the private members of each other.
The detailed implementation when the runtime environment does not support the proposal:
const A = (function () {
// use a closure to ensure that the weak map cannot be accessed outside the class A
const privates = new WeakMap();
return class {
constructor() {
privates.set(this, {['#private'] : 'unreachable value?'});
}
getPrivateValue() {
return privates.get(this)['#private'];
}
}
})();
const a = new A();
// the private member can only be accessed via an exposed method
a.getPrivateValue(); // => "unreachable value?"