JS: classes, factory functions, private fields

Hey all. I am learning JS at the moment and I have learned about classes and how all their fields are public by default. I have also learned that it is not always wanted so a past workarounds included: - adding "_" prefix to the key name for other developers to understand your intentions, - using factory functions Nowadays "#" is supported to be used for this. My questions: 1. Why is it important that certain/all keys are private and not accessible? Do you have a real-life example of this? 2. Now that "#" prefix is implemented and widely supported, do people even use factory functions these days? Why (whether yes or no)? 3. If we expect some key values to change, why should we create extra functions to access those (getters, setters) instead of using the dot-notation? Why is dot-notation problematic? Note: Please no links to MDN, I have been there, it is not understandable to me. It has been written for people who already understand it or already have some experience in the field, not for newbies like me. Thanks.
6 Replies
Joao
Joao12mo ago
1. The purpose of making a distinction between private and public fields is aimed towards team development. For example I write a class and one day I'm sick and you take over the part of the code that I'm usually responsible of. When you see some private methods you immediately know that those are only meant to be used within the class. This is partly related to your other point of getters and setters, and it may sometimes feel counterintuitive because as you say, isn't is easier to use dot notation directly? Well, things are done in a particular way (read: design patterns) for a reason. If I'm stopping you from changing things directly by making a certain property private, then you are forced to do things "the right way". And, of course, "right way" is relative to every project, every team, every organization. But hopefully you get the idea. I may not be entirely obvious to you right now but having this classifications is meant to keep things organized. Another example of this is from the consumer's perspective. Your code editor will not provide auto-completion and code suggestions for private methods, so you are not as confused as how you are supposed to use this class from an npm package you just downloaded. 3. (I'll answer this first since it's related to the above) The point of getters and setters is once again to make explicit how you should interact with this class, both as a consumer and as a fellow developer. In addition, you can hook up additional logic to these otherwise straight forward read/write operations. For example, I can run validation checks whenever you try to update some value. Or I can make a network request to some logging system whenever you try to read a specific piece of sensitive data. 2. I think the factory design pattern is extremely popular. But it's probably true that languages like Java use it more often since those are heavily reliant on Object Oriented Programming. There may be many reasons why people want to both use this or avoid it, it's entirely a design decision. I don't use it very often personally, so I can't really say much more about this.
13eck
13eck12mo ago
A lot of what I'm about to say should be prefixed with, "as Joao mentioned" :p 1. Private keys are to be used internally by the class and not for the consumer to modify/access. If the end-user of the class doesn't need to use the field, it can be private 2. Some do, but mostly from a legacy perspective. That's what they're used to and/or it's what their existing codebase uses. Also, the class syntax is just a wrapper/syntactic sugar over constructor functions anyway, so it's the same thing at the end of the day for the JS interpreter. Class syntax looks nicer to the dev 3. This goes back to answer the first: internal data change that isn't to be directly modified by the user of the class might still need to be able to view the data, so you use a getter. An example is immutable state: you don't want the user to be able to modify the state directly, so you set the #state field to be private—but they should be able to see what the state is, so you make a getter for it (that does a deep copy to prevent mutating state by changing the gotten data) A lot of tutorials will not bother with private fields b/c the tutorial is "basic" and not production ready, and those tuts also are only for them to use and not making a class to share with the world so they (usually) know what they're doing. But if you take a few moments to think, "should the end-user be able to directly change this?" and the answer is "no" then it should be a private field
Zenon
Zenon12mo ago
Thank you both, I think I understand it from an "outside" perspective. Some follow-up questions regarding factory functions with the following example: function pizzaFactory(pizzaSize) { let crust = "original" const size = pizzaSize return { bake: () => console.log(Baking a ${size} ${crust} crust pizza), setCrust: (newCrust) => crust = newCrust } } I am just testing around and it seems like I can still write values to crust after creating an object with: const factoryPizza = pizzaFactory("small") If I understand it correctly, factoryPizza is an object returned by the pizzaFactory function, meaning it has two methods. I don"t understand how this works. The bake() function will print something to the console based on the crust and size variables, but where are they? They are not in the object. How are they staying in the memory? And even worse, I seem to be able to write and change eg the crust variable, even though its a function variable. Do all functions like this load and stay in the memory? I thought that a function runs, does whatever it needs to do before returning a value, returns a value and is then "forgotten". But this doesn't seem to be the case. Or are those values in our factoryPizza object, just somewhat hidden, and the function has indeed ran its course and is no longer in the memory? What is also interesting to note is that, after creating a second pizza by calling that function, the crust value of the first pizza doesn't reset, meaning it IS stored specifically for that first pizza object.
13eck
13eck12mo ago
The bake() function will print something to the console based on the crust and size variables, but where are they? They are not in the object.
It's called a "closure". The pizzaFactory() function closes over the let crust and const size variables. Anything inside the function can access those variables without any issues at all. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
How are they staying in the memory?
This is part of the closure but also part of JS being a garbage collected language. All variables, functions, etc are stored somwhere in memory when created and are only removed when there are no external points to it. As long as a variable can be reached via the global scope they survive. Your factoryPizza object's bake() method can reach both variables, so they're not garbage-collected.
And even worse, I seem to be able to write and change eg the crust variable, even though its a function variable.
You can because you made the factory do that. If you removed the setCrust() method then you'd not be able to change it. Note that if you did:
const factoryPizza = pizzaFactory("small");
factoryPizza.bake();
factoryPizza.crust = "deep dish";
factoryPizza.bake();
const factoryPizza = pizzaFactory("small");
factoryPizza.bake();
factoryPizza.crust = "deep dish";
factoryPizza.bake();
Both will print "Baking a small original crust pizza", as you can't reach into the closure to change the variables.
Do all functions like this load and stay in the memory? I thought that a function runs, does whatever it needs to do before returning a value, returns a value and is then "forgotten". But this doesn't seem to be the case. Or are those values in our factoryPizza object, just somewhat hidden, and the function has indeed ran its course and is no longer in the memory?
See above about closures and garbage collection
Zenon
Zenon12mo ago
Thanks a lot vanilla, this was very understandable.
13eck
13eck12mo ago
Glad my ramblings make sense! 😁