By combining decorators and the ref
proposal, we could
conceivably end up with a workable solution for "friend"-like behavior with classes:
// a.js
import { B } from "./b.js";
@friend(ref B) // give B access to all private fields of A
class A {
#x;
}
// b.js
import { A } from "./a.js";
let backchannel;
@friend.accept(ref A, ref backchannel)
class B {
getX(a) {
return backchannel.get(a, "#x");
}
}
Using ref
would allow us to mostly avoid issues with circularity and TDZ (since it creates a reference to the binding
and not the value). The friend.accept
decorator effectively creates a link between A
and B
that is exposed via backchannel
.
The backchannel
object then lazily verifies that B
was granted friend access to A
.
It can, yes, but isn't as terse and has some footguns. For example, one value of
ref
is that it avoids (or rather defers) TDZ since it creates a reference to the binding (even if uninitialized) rather than the value. To do this today you would need to create a closure, e.g.() => B
instead ofB
. However, its fairly easy for someone to inadvertently writeB
instead of the closure, and its impossible to correctly determine whether the thing passed in is a closure forB
or justB
since both have atypeof
of"function"
(andB
could have been written as an ES5-style "class").