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
@@ -768,32 +768,33 @@ If I'm being honest: maybe you shouldn't. Or maybe you should. That's up to you.
768
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
769
770
770
```js
771
-
classSomethingStrange {
772
-
#mySecretNumber =42
771
+
classPoint2d {
772
+
// statics
773
+
staticsamePoint(point1,point2) {
774
+
return point1.#ID=== point2.#ID;
775
+
}
773
776
774
-
#changeMySecret() {
775
-
// ooo, tricky tricky!
776
-
this.#mySecretNumber *=Math.random();
777
+
// privates
778
+
#ID=null
779
+
#assignID() {
780
+
this.#ID=Math.round(Math.random() *1e9);
777
781
}
778
782
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
-
}
783
+
// publics
784
+
x
785
+
y
786
+
constructor(x,y) {
787
+
this.#assignID();
788
+
this.x= x;
789
+
this.y= y;
787
790
}
788
791
}
789
792
790
-
var thing =newSomethingStrange();
791
-
792
-
this.guessMySecret(42);
793
-
// You win!!
793
+
var one =newPoint2d(3,4);
794
+
var two =newPoint2d(3,4);
794
795
795
-
this.guessMySecret(42);
796
-
// Nope, try again.
796
+
Point2d.samePoint(one,two); // false
797
+
Point2d.samePoint(one,one); // true
797
798
```
798
799
799
800
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)
@@ -806,99 +807,247 @@ The `#whatever` syntax (including `this.#whatever` form) is only valid inside `c
806
807
807
808
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
809
-
#### Exfiltration
810
+
#### Subclassing + Privates
810
811
811
-
Even though a method or member may be declared with *private* visibility, they can still be exfiltrated (extracted) from a class instance:
812
+
I warned earlier that subclassing with classes that have private members/methods can be a limiting trap. But that doesn't mean they cannot be used together.
812
813
813
-
```js
814
-
var number, func;
814
+
Because "inheritance" in JS is sharing (through the `[[Prototype]]` chain), if you invoke an inherited method in a subclass, and that inherited method in turn accesses/invokes privates in its host (base) class, this works fine:
815
815
816
-
classSomethingStrange {
817
-
#myPrivateNumber =42
818
-
#mySecretFunc() {
819
-
returnthis.#myPrivateNumber;
820
-
}
816
+
```js
817
+
classPoint2d { /* .. */ }
821
818
822
-
constructor() {
823
-
number =this.#myPrivateNumber;
824
-
func =this.#mySecretFunc;
819
+
classPoint3dextendsPoint2d {
820
+
z
821
+
constructor(x,y,z) {
822
+
super(x,y);
823
+
this.z= z;
825
824
}
826
825
}
827
826
828
-
var thing =newSomethingStrange();
827
+
var one =newPoint3d(3,4,5);
828
+
```
829
+
830
+
The `super(x,y)` call in this constructor invokes the inherited base class constructor (`Point2d(..)`), which itself accesses `Point2d`'s private method `#assignID()` (see the earlier snippet). No exception is thrown, even though `Point3d` cannot directly see or access the `#ID` / `#assignID()` privates that are indeed stored on the instance (named `one` here).
829
831
830
-
number; // 42
831
-
func; // function #mySecreFunc() { .. }
832
-
func.call(thing); // 42
832
+
In fact, even the inherited `static samePoint(..)` function will work from either `Point3d` or `Point2d`:
833
833
834
-
func.call({});
835
-
// TypeError: Cannot read private member #myPrivateNumber
836
-
// from an object whose class did not declare it
834
+
```js
835
+
Point2d.samePoint(one,one); // true
836
+
Point3d.samePoint(one,one); // true
837
+
```
838
+
839
+
Actually, that shouldn't be that suprising, since:
840
+
841
+
```js
842
+
Point2d.samePoint===Point3d.samePoint;
843
+
```
844
+
845
+
The inherited function reference is *the exact same function* as the base function reference; it's not some copy of the function. Because the function in question has no `this` reference in it, no matter from where it's invoked, it should produce the same outcome.
846
+
847
+
It's still a shame though that `Point3d` has no way to access/influence, or indeed even knowledge of, the `#ID` / `#assignID()` privates from `Point2d`:
848
+
849
+
```js
850
+
classPoint2d { /* .. */ }
851
+
852
+
classPoint3dextendsPoint2d {
853
+
z
854
+
constructor(x,y,z) {
855
+
super(x,y);
856
+
this.z= z;
857
+
858
+
console.log(this.#ID); // will throw!
859
+
}
860
+
}
837
861
```
838
862
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.
863
+
| WARNING: |
864
+
| :--- |
865
+
| Notice that this snippet throws an early static syntax error at the time of defining the `Point3d` class, before ever even getting a chance to create an instance of the class. The same exception would be thrown if the reference was `super.#ID` instead of `this.#ID`. |
840
866
841
867
#### Existence Check
842
868
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.
869
+
Keep in mind that only the `class` itself knows about, and can therefore check for, such a private field/method.
870
+
871
+
You may want to check to see if a private field/method exists on an object instance. For example (as shown below), you may have a static function or method in a class, which receives an external object reference passed in. To check to see if the passed-in object reference is of this same class (and therefore has the same private members/methods in it), you basically need to do a "brand check" against the object.
844
872
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:
873
+
Such a check could be rather convoluted, because if you access a private field that doesn't already exist on the object, you get a JS exception thrown, requiring ugly `try..catch` logic.
874
+
875
+
But there's a cleaner approach, so called an "ergonomic brand check", using the `in` keyword:
846
876
847
877
```js
848
-
classSomethingStrange {
849
-
staticcheckGuess(thing,v) {
850
-
// "ergonomic brand check"
851
-
if (#myPrivateNumber in thing) {
852
-
returnthing.guessMySecret(v);
878
+
classPoint2d {
879
+
// statics
880
+
staticsamePoint(point1,point2) {
881
+
// "ergonomic brand checks"
882
+
if (#IDin point1 && #IDin point2) {
883
+
return point1.#ID=== point2.#ID;
853
884
}
885
+
returnfalse;
854
886
}
855
887
856
-
#myPrivateNumber =42;
888
+
// privates
889
+
#ID=null
890
+
#assignID() {
891
+
this.#ID=Math.round(Math.random() *1e9);
892
+
}
857
893
858
-
// ..
894
+
// publics
895
+
x
896
+
y
897
+
constructor(x,y) {
898
+
this.#assignID();
899
+
this.x= x;
900
+
this.y= y;
901
+
}
902
+
}
903
+
904
+
var one =newPoint2d(3,4);
905
+
var two =newPoint2d(3,4);
906
+
907
+
Point2d.samePoint(one,two); // false
908
+
Point2d.samePoint(one,one); // true
909
+
```
910
+
911
+
The `#privateField in someObject` check will not throw an exception if the field isn't found, so it's safe to use without `try..catch` and use its simple boolean result.
912
+
913
+
#### Exfiltration
914
+
915
+
Even though a member/method may be declared with *private* visibility, it can still be exfiltrated (extracted) from a class instance:
916
+
917
+
```js
918
+
var id, func;
919
+
920
+
classPoint2d {
921
+
// privates
922
+
#ID=null
923
+
#assignID() {
924
+
this.#ID=Math.round(Math.random() *1e9);
925
+
}
926
+
927
+
// publics
928
+
x
929
+
y
930
+
constructor(x,y) {
931
+
this.#assignID();
932
+
this.x= x;
933
+
this.y= y;
934
+
935
+
// exfiltration
936
+
id =this.#ID;
937
+
func =this.#assignID;
938
+
}
859
939
}
860
940
861
-
varthing=newSomethingStrange();
941
+
varpoint=newPoint2d(3,4);
862
942
863
-
SomethingStrage.checkGuess(thing,42);
864
-
// You win!!
943
+
id; // 7392851012 (...for example)
944
+
945
+
func; // function #assignID() { .. }
946
+
func.call(point,42);
947
+
948
+
func.call({},100);
949
+
// TypeError: Cannot write private member #ID to an
950
+
// object whose class did not declare it
865
951
```
866
952
953
+
The main concern here is to be careful when passing private methods as callbacks (or in any way exposing privates to other parts of the program). There's nothing stopping you from doing so, which can create a bit of an unintended privacy disclosure.
954
+
867
955
### Private Statics
868
956
869
957
Static properties and functions can also use `#` to be marked as private:
870
958
871
959
```js
872
-
classSomethingStrange {
873
-
static #errorMsg ="Not available."
960
+
classPoint2d {
961
+
static #errorMsg ="Out of bounds."
874
962
static #printError() {
875
-
console.log(this.#errorMsg);
963
+
console.log(`Error: ${this.#errorMsg}`);
876
964
}
877
965
878
-
staticcheckGuess(thing,v) {
879
-
// "ergonomic brand check"
880
-
if (#myPrivateNumber in thing) {
881
-
returnthing.guessMySecret(v);
882
-
}
883
-
else {
884
-
this.#printError();
966
+
// publics
967
+
x
968
+
y
969
+
constructor(x,y) {
970
+
if (x >100|| y >100) {
971
+
Point2d.#printError();
885
972
}
973
+
this.x= x;
974
+
this.y= y;
975
+
}
976
+
}
977
+
978
+
var one =newPoint2d(30,400);
979
+
// Error: Out of bounds.
980
+
```
981
+
982
+
The `#printError()` static private function here has a `this`, but that's referencing the `Point2d` class, not an instance. As such, the `#errorMsg` and `#printError()` are independent of instances and thus are best as statics. Moreover, there's no reason for them to be accessible outside the class, so they're marked private.
983
+
984
+
Remember: private statics are similarly not-inherited by subclasses just as private members/methods are not.
985
+
986
+
#### Subclassing Gotcha
987
+
988
+
Recall that inherited methods, invoked from a subclass, have no trouble accessing (via `this.#whatever` style references) any privates from their own base class:
989
+
990
+
```js
991
+
classPoint2d {
992
+
// ..
993
+
994
+
getID() {
995
+
returnthis.#ID;
886
996
}
887
997
888
-
#myPrivateNumber =42;
998
+
// ..
999
+
}
889
1000
1001
+
classPoint3dextendsPoint2d {
890
1002
// ..
1003
+
1004
+
printID() {
1005
+
console.log(`ID: ${this.getID()}`);
1006
+
}
891
1007
}
1008
+
1009
+
var point =newPoint3d(3,4,5);
1010
+
point.printID();
1011
+
// ID: ..
892
1012
```
893
1013
894
-
Private statics are similarly not-inherited just as private members/methods are not.
1014
+
That works just fine.
895
1015
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. |
1016
+
Unfortunately, and (to me) a little unexpectedly/inconsistently, the same is not true of private statics accessed from inherited public static functions:
1017
+
1018
+
```js
1019
+
classPoint2d {
1020
+
static #errorMsg ="Out of bounds."
1021
+
staticprintError() {
1022
+
console.log(`Error: ${this.#errorMsg}`);
1023
+
}
1024
+
1025
+
// ..
1026
+
}
1027
+
1028
+
classPoint3dextendsPoint2d {
1029
+
// ..
1030
+
}
1031
+
1032
+
Point2d.printError();
1033
+
// Error: Out of bounds.
1034
+
1035
+
Point3d.printError===Point2d.printError;
1036
+
// true
1037
+
1038
+
Point3d.printError();
1039
+
// TypeError: Cannot read private member #errorMsg
1040
+
// from an object whose class did not declare it
1041
+
```
1042
+
1043
+
The `printError()` static is inherited (shared via `[[Prototype]]`) from `Point2d` to `Point3d` just fine, which is why the function references are identical. Like the non-static snippet just above, you might have expected the `Point3d.printError()` static invocation to resolve via the `[[Prototype]]` chain to its original base class (`Point2d`) location, thereby letting it access the base class's `#errorMsg` static private.
1044
+
1045
+
But it fails, as shown by the last statement in that snippet. Beware that gotcha!
899
1046
900
1047
## Class Example
901
1048
1049
+
OK, we've laid out a bunch of disparate class features. I want to wrap up this chapter by trying to illustrate a sampling of these capabilities in a single example that's a little less basic/contrived.
1050
+
902
1051
// TODO
903
1052
904
1053
[^POLP]: *Principle of Least Privilege*, https://en.wikipedia.org/wiki/Principle_of_least_privilege, 15 July 2022.
0 commit comments