Classes & 'access modifiers' in javascript
First I'll address this bit:
...and how I can apply my knowledge of traditional, typed object oriented languages like Java to javascript.
You can frequently get away with thinking of JavaScript as being a bit like Java/C#/etc., but if you do:
It'll bite you at some point, because JavaScript is not like Java/C#/etc., it is fundamentally different even though it has some trappings that make it look similar;
and
You'll miss out on the true power of JavaScript by not knowing how to use its fundamentally-different nature.
How do I know? I did exactly that, came from a Java (and even C++) background and assumed JavaScript was similar, and got bitten, and missed out (initially). :-)
That's not remotely meant as any form of criticism, it's just a word to the wise. Try to learn in depth how JavaScript prototypical inheritance and closures work, and you'll find that the way you solve problems will be slightly different than the way you would in Java/C#/etc.
In Old Environments
Answers to your direct queries inline in this code block, but in 2022 I added a bit below ("In Modern Environments") to address the new modern features recently added to JavaScript to directly address these use cases in different ways.
var MyClass = (function () {
// [What am I?] A private variable?
// ==> A private *class* variable (static variable). Not instance-specific.
var myVariable1
// Constructor.
function MyClass() {
// Essentially a public variable.
// ==> A public field/property/variable, choose your terminology. :-)
// ==> The Java spec calls them both fields and variables (oddly).
// ==> In JavaScript, they're called properties.
this.myVariable2 = 0;
}
// Public method returning myVariable1.
// ==> Correct, but note that myVariable1 is shared across all instances
MyClass.prototype.myMethod1 = function () {
return myVariable1;
};
// Public method returning public variable.
// ==> Yes, note that this one is returning an instance-specific field/property/variable
MyClass.prototype.myMethod2 = function () {
return this.myVariable2;
};
// [What am I?] A private method?
// ==> A private *class* method (static method), yes.
function myMethod3 () {
return 0;
}
return MyClass;
}());
I am mostly wondering about the "private" stuff. Like, what happens if I have multiple instances of this class? Can they interfere with each others private variables and functions?
Yes, as I mentioned in the comments, they're class-wide. If you wanted to have a private "instance field", you'd have to declare it within your constructor, and create any functions that needed to have access to it within the constructor so that they closed over it:
function MyClass(arg) {
var privateInstanceInfo = arg; // We could also just use arg directly
this.accessThePrivateInstanceInfo = function() {
return privateInstanceInfo * 42;
};
}
MyClass.prototype.useOnlyPublicInfo = function() {
// This cannot access `privateInstanceInfo` directly.
// It *can* access the function `accessThePrivateInstanceInfo`
// because that's public:
return accessThePrivateInstanceInfo();
};
var c = new MyClass(2);
console.log(c.useOnlyPublicInfo()); // 84
This is all down to a concept called closures, which is outlined in detail in this question and its answers: How do JavaScript closures work? I'll also reference my article from several years ago Closures are not complicated, which while it uses some older terms for things compared to the latest specification, still describes the concepts just fine.
The inline-invoked function (IIFE) you've used as a wrapper creates a single execution context that all of the functions within it close over. That means they have live access to the variables and functions defined within that context, even after the function returns. Since you only call it once, there's only one context, and so only one myVariable1
and myMethod3
.
In my example creating private instance information, we use the fact that a new context is created for each call to the constructor. That context isn't shared with anything else, and so becomes instance-specific.
There's a way to get near-private instance properties without having to define functions in the constructor, which is to use a randomly-selected name:
function makeRandomName() {
var str = "";
while (str.length < 10) {
str += String.fromCharCode(32 + Math.floor(95 * Math.random()));
}
return "__" + str;
}
var MyClass = (function() {
var pseudoPrivatePropName = makeRandomName();
function MyClass() {
this[pseudoPrivatePropName] = 42;
}
// ....
return MyClass;
})();
The code inside the IIFE knows the name of the property, the code outside of it doesn't. Now, malicious code could still find the property, but it's become much more challenging, particularly if you have more than one. (And if you have more than one, you need a better name allocator than the one above.) (Details in this very-outdated article in my blog.)
In ES2015 (ES6) you could use
var pseudoPrivatePropName = Symbol();
...instead of having a name allocator, but it doesn't make the property any more private.
In Modern Environments
In modern environments though, you can use new features that have been added to JavaScript for the ES2022 specification:
- Class Public Instance Fields & Private Instance Fields
- Private instance methods and accessors
- Static class fields and private static methods
Here's how you might right MyClass
using those features and ES2015's class
syntax:
class MyClass {
// Static class field (class-wide, not instance-specific)
static myVariable1;
// Instance class field (property) (instance-specific)
myVariable2 = 0;
// Private class field (instance-specific)
#privateInstanceInfo;
// Private static class field (class-wide, not instance-specific)
static #privateStaticInfo;
// Constructor for the class
constructor(arg) {
this.#privateInstanceInfo = arg;
}
// Public instance method returning static member
myMethod1() {
// Note that `MyClass.myVariable1` is shared across all instances
return MyClass.myVariable1;
}
// Public instance method return instance field (property) value
myMethod2() {
return this.myVariable2;
}
// Public instance method accessing the private field; all methods declared
// for the class can access it, not just this one
accessThePrivateInstanceInfo() {
return this.#privateInstanceInfo * 42;
}
// Private instance method
#myMethod3() {
return 0;
}
// Static method
static myStaticMethod() {
return this.#getPrivateStaticInfo(); // Or `MyClass.#getPrivateStaticInfo();`
}
// Private static method
static #getPrivateStaticInfo() {
return this.#privateStaticInfo; // Or `MyClass.#privateStaticInfo`
}
}
On those last two, the static method and the private static method, note that usually this
during the call will refer to the class constructor itself (MyClass
), because usually you're calling static members by doing MyClass.myStaticMethod()
. But as usual with this
, that's not necessarily true; if you did const f = MyClass.myStaticMethod; f();
, this
during the call wouldn't refer to MyClass
. Using this
in static methods to refer to the constructor is friendly to subclasses (because a subclass constructor's prototype is its superclass constructor; details here), but does mean you have to deal with the usual caveats around this
.
Use # for private, and _ for protected.