JavaScript “protected” properties
Private JS fields are both cool and “wasteful”, let me expand on that.
class A {
#value;
constructor(value) {
this.#value = value;
}
}
class B extends A {
#value;
constructor(value) {
super();
this.#value = value;
}
}If we create new B('secret') the instance will have 2 private fields:
- the
#valuethat is defined through theAclass, a reference that onlyAclass definition can reach/change/read/use and that will throw if anything inBtries to reach it, as insuper.#value— nope nopity nope, that will be a SyntaxError: Unexpected private field - the
#valuethat is defined through theBclass, a reference that onlyBclass definition can reach/change/read/use
The “wasteful” part of this specification can be described as such:
- we need accessors to eventually be able to at least read that super value but accessors fully invalidate the secret/private nature of that field, if the reason to expose these is to have a way for the inherited class to deal with its inherited internals
- the field needs to use “two private slots” instead of one, something that the
protectedkeyword would’ve solved, something that does not exist (yet?) in the JavaScript Programming Language
// ⚠️ this does not exist/work
class A {
// @protected meta example
&value;
constructor(value) {
this.&value = value;
}
}
class B extends A {
log() {
console.log(this.&value);
}
}
new B('protected').log();
// 'protected'A workaround for protected fields
Because only at class definition time we can access private fields, what if we wrap such access in a way that is still “private”, at least per module scope, and grants either read or write access?
// 🥳 this works wonderfully
class A {
// #value read/write
static value(self, ..._) {
if (_.length) [self.#value] = _;
return self.#value;
}
#value;
constructor(value) {
this.#value = value;
}
}
// extract the static method and ...
const { value } = A;
// ... erase the static method !!!
delete A.value;
// ... there we go ...
class B extends A {
log() {
console.log(value(this));
// change value via
// value(this, 'change')
}
}
new B('secret').log();
// 'secret'… plus a quick helper …
Because forgetting to remove the static field might happen and because this pattern works also for private methods, wouldn’t be cool to use a tiny helper that helps us extracting such properties/fields?
const _protected = Class => new Proxy(Class, {
get(Class, staticField) {
const value = Class[staticField];
delete Class[staticField];
return value;
}
});
const { value, method } = _protected(A);Update: an even simpler approach 🥳
Huge thanks @tombl_ for pointing out there’s not even a need to delete the static field because a static block would work the same:
let value;
class A {
static {
// #value read/write
value = (self, ..._) => {
if (_.length) [self.#value] = _;
return self.#value;
};
}
#value;
constructor(value) {
this.#value = value;
}
}
class B extends A {
log() {
console.log(value(this));
}
}
new B('secret').log();
// 'secret'Gotta admit I often forgot about the static block in classes, this makes the helper obsolete already, which is great!
And “that’s all folks”, if you ever need to expose internally or within a private scope private fields, now you know a little trick to do so!
Enjoy JS 👋
