You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: objects-classes/ch3.md
+183Lines changed: 183 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -719,4 +719,187 @@ It's also interesting, perhaps only historically now, to note that static inheri
719
719
720
720
## Private Class Behavior
721
721
722
+
Everything we've discussed so far as part of a `class` definition is publicly visible/accessible, either as static properties/functions on the class, methods on the constructor's `prototype`, or member properties on the instance.
723
+
724
+
But how do you store information that cannot be seen from outside the class? This was one of the most asked for features, and biggest complaints with JS's `class`, up until it was finally addressed in ES2022.
725
+
726
+
`class` now supports new syntax for declaring private fields (instance members) and private methods. In addition, private static properties/functions are possible.
727
+
728
+
### Motivation?
729
+
730
+
Before we illustrate how to do `class` privates, it bears contemplating why this is a helpful feature?
731
+
732
+
With closure-oriented design patterns (again, see the "Scope & Closures" book of this series), we automatically get "privacy" built-in. When you declare a variable inside a scope, it cannot be seen outside that scope. Period. Reducing the scope visibility of a declaration is helpful in preventing namespace collisions (identical variable names).
733
+
734
+
But it's even more important to ensure proper "defensive" design of software, the so called "Principle of Least Privilege" [^POLP]. POLP states that we should only expose a piece of information or capability in our software to the smallest surface area necessary.
735
+
736
+
Over-exposure opens our software up to several issues that complicate software security/maintenance, including another piece of code acting maliciously to do something our code didn't expect or intend. Moreover, there's the less critical but still as problematic concern of other parts of our software relying on (using) parts of our code that we should have reserved as hidden implementation detail. Once other code relies on our code's implementation details, we stop being able to refactor our code without potentially breaking other parts of the program.
737
+
738
+
So, in short, we *should* hide implementation details if they're not necessary to be exposed. In this sense, JS's `class` system feels a bit too permissive in that everything defaults to being public. Class-private features are a welcomed addition to more proper software design.
739
+
740
+
#### Too Private?
741
+
742
+
All that said, I have to throw a bit of a damper on the class-private party.
743
+
744
+
I've suggested strongly that you should only use `class` if you're going to really take advantage of most or all of what class-orientation gives you. Otherwise, you'd be better suited using other core pillar features of JS for organizing code, such as with the closure pattern.
745
+
746
+
One of the most important aspects of class-orientation is subclass inheritance, as we've seen illustrated numerous times so far in this chapter. Guess what happens to a private member/method in a base class, when it's extended by a subclass?
747
+
748
+
Private members/methods are private **only to the class they're defined in**, and are **not** inherited in any way by a subclass. Uh oh.
749
+
750
+
That might not seem like too big of a concern, until you start working with `class` and private members/methods in real software. You might quickly run up against a situation where you need to access a private method, or more often even, just a private member, from the subclass, so that the subclass can extend/augment the behavior of the base class as desired. And you might scream in frustration pretty quickly once you realize this is not possible.
751
+
752
+
What comes next is inevitably an awkward decision: do you just go back to making it public, so the subclass can access it? Ugh. Or, worse, do you try to re-design the base class to contort the design of its members/methods, such that the lack of access is partially worked around. That often involves exhausting over-parameterization (with privates as default parameter values) of methods, and other such tricks. Double ugh.
753
+
754
+
There's not a particularly great answer here, to be honest. If you have experience with class-orientation in more traditional class languages like Java or C++, you're probably dubious as to why we don't have *protected* visibility in between *public* and *private*. That's exactly what *protected* is for: keeping something private to a class AND any of its subclasses. Those languages also have *friend* features, but that's beyond the scope of our discussion here.
755
+
756
+
Sadly, not only does JS not have *protected* visibility, it seems (even as useful as it is!) to be unlikely as a JS feature. It's been discussed in great detail for over a decade (before ES6 was even a thing), and there've been multiple proposals for it.
757
+
758
+
I shouldn't say it will *never* happen, because that's not solid ground to stake on in any software. But it's very unlikely, because it actually betrays the very pillar that `class` is built on. If you are curious, or (more likely) certain that there's just *got to be a way*, I'll cover the incompatibility of *protected* visibility within JS's mechanisms in an appendix.
759
+
760
+
The point here is, as of now, JS has no *protected* visibility, and it won't any time soon. And *protected* visibility is actually, in practice, way more useful than *private* visibility.
761
+
762
+
So we return to the question: **Why should you care to make any `class` contents private?**
763
+
764
+
If I'm being honest: maybe you shouldn't. Or maybe you should. That's up to you. Just go into it aware of the stumbling blocks.
765
+
766
+
### Private Members/Methods
767
+
768
+
You're excited to finally see the syntax for magical *private* visibility, right? Please don't shoot the messenger if you feel angered or sad at what you're about to see.
769
+
770
+
```js
771
+
classSomethingStrange {
772
+
#mySecretNumber =42
773
+
774
+
#changeMySecret() {
775
+
// ooo, tricky tricky!
776
+
this.#mySecretNumber *=Math.random();
777
+
}
778
+
779
+
guessMySecret(v) {
780
+
if (v ===this.#mySecretNumber) {
781
+
console.log("You win!");
782
+
this.#changeMySecret();
783
+
}
784
+
else {
785
+
console.log("Nope, try again.");
786
+
}
787
+
}
788
+
}
789
+
790
+
var thing =newSomethingStrange();
791
+
792
+
this.guessMySecret(42);
793
+
// You win!!
794
+
795
+
this.guessMySecret(42);
796
+
// Nope, try again.
797
+
```
798
+
799
+
No, JS didn't do the sensible thing and introduce a `private` keyword like they did with `static`. Instead, they introduced the `#`. (insert lame joke about social-media millienials loving hashtags, or something)
800
+
801
+
| TIP: |
802
+
| :--- |
803
+
| And yes, there's a million and one discussions about why not. I could spend chapters recounting the whole history, but honestly I just don't care to. I think this syntax is ugly, and many others do, too. And some love it! If you're in the latter camp, though I rarely do something like this, I'm just going to say: **just accept it**. It's too late for any more debate or pleading. |
804
+
805
+
The `#whatever` syntax (including `this.#whatever` form) is only valid inside `class` bodies. It will throw syntax errors if used outside of a `class`.
806
+
807
+
Unlike public fields/instance members, private fields/instance members *must* be declared in the `class` body. You cannot add a private member to a class declaration dynamically while in the constructor method; `this.#whatever = ..` type assignments only work if the `#whatever` private field is declared in the class body. Moreover, though private fields can be re-assigned, they cannot be `delete`d from an instance, the way a public field/class member can.
808
+
809
+
#### Exfiltration
810
+
811
+
Even though a method or member may be declared with *private* visibility, they can still be exfiltrated (extracted) from a class instance:
812
+
813
+
```js
814
+
var number, func;
815
+
816
+
classSomethingStrange {
817
+
#myPrivateNumber =42
818
+
#mySecretFunc() {
819
+
returnthis.#myPrivateNumber;
820
+
}
821
+
822
+
constructor() {
823
+
number =this.#myPrivateNumber;
824
+
func =this.#mySecretFunc;
825
+
}
826
+
}
827
+
828
+
var thing =newSomethingStrange();
829
+
830
+
number; // 42
831
+
func; // function #mySecreFunc() { .. }
832
+
func.call(thing); // 42
833
+
834
+
func.call({});
835
+
// TypeError: Cannot read private member #myPrivateNumber
836
+
// from an object whose class did not declare it
837
+
```
838
+
839
+
The main reason for me pointing this out is to be careful when using private methods as callbacks (or in any way passing them to other parts of the program). There's nothing stopping you from doing so, which can create a bit of an unintended privacy disclosure.
840
+
841
+
#### Existence Check
842
+
843
+
I think this use-case is somewhat contrived/unusual, but... you may want to check to see if a private field/method exists on an object (including the current `this` instance). Keep in mind that only the `class` itself knows about, and can therefore check for, any such a private field/method; such checks are almost always going to be in a static function.
844
+
845
+
Doing could be rather convoluted, because if you access a private field that didn't already exist, you get a JS exception thrown, which would lead to ugly `try..catch` logic. But there's a cleaner approach:
846
+
847
+
```js
848
+
classSomethingStrange {
849
+
staticcheckGuess(thing,v) {
850
+
// "ergonomic brand check"
851
+
if (#myPrivateNumber in thing) {
852
+
returnthing.guessMySecret(v);
853
+
}
854
+
}
855
+
856
+
#myPrivateNumber =42;
857
+
858
+
// ..
859
+
}
860
+
861
+
var thing =newSomethingStrange();
862
+
863
+
SomethingStrage.checkGuess(thing,42);
864
+
// You win!!
865
+
```
866
+
867
+
### Private Statics
868
+
869
+
Static properties and functions can also use `#` to be marked as private:
870
+
871
+
```js
872
+
classSomethingStrange {
873
+
static #errorMsg ="Not available."
874
+
static #printError() {
875
+
console.log(this.#errorMsg);
876
+
}
877
+
878
+
staticcheckGuess(thing,v) {
879
+
// "ergonomic brand check"
880
+
if (#myPrivateNumber in thing) {
881
+
returnthing.guessMySecret(v);
882
+
}
883
+
else {
884
+
this.#printError();
885
+
}
886
+
}
887
+
888
+
#myPrivateNumber =42;
889
+
890
+
// ..
891
+
}
892
+
```
893
+
894
+
Private statics are similarly not-inherited just as private members/methods are not.
895
+
896
+
| WARNING: |
897
+
| :--- |
898
+
| Be careful with `this` references inside public static functions that make reference to private static functions. While the public static functions will be inherited by derived subclasses, the private static functions are not, which will cause such `this.#..` references to fail. |
899
+
900
+
## Class Example
901
+
722
902
// TODO
903
+
904
+
[^POLP]: *Principle of Least Privilege*, https://en.wikipedia.org/wiki/Principle_of_least_privilege, 15 July 2022.
0 commit comments