User is still just a function, and
So while it’s generally safe to go about thinking of these instances as classes, remember that they’re still just objects at heart. The constructor simply defines the type while the prototype describes the behavior. For more, Nicholas Zakas wrote a great post on custom types that goes into this distinction in greater detail.
Back to the original question: How do you export a custom type – an entirely different paradigm than the singleton – while maintaining those same concepts for cleaner code?
When reading a new module, the first thing you need to do is find out whats being exported. In large files this isn’t always obvious, so go out of your way to make it explicit. To do this, tie
module.exports to the constructor at the very top of your “Public” section.
This gives you an easy separation of private and public, a single point of truth for what is being exported, and a nice name for your type.
Now you can require this constructor anywhere in our application and easily create a new user.
All object properties are publicly accessible, so adding them to a new instance is easy (for example: calling
bob.name will return ‘Bob’ in the example above). Private properties, however, aren’t so simple. Consider the example below which tries to use private variables:
It is true that the ‘paid’ variable is now private, and checking
bob.paid will return undefined. But paid is also no longer unique to each User. Each time
togglePaid() is called on any user it will toggle the value of ‘paid’ for all users.
require() caches each module, so only one of those variables exists in our application and all Users are referencing it.
We need private variables to live as properties of an instance, but all properties are public. Out of options, defer to a semantic separation. Use the following naming convention to distinguish between public and private:
The dangling underscore tells a developer that this property is private to the implementation, and isn’t meant to be referenced directly outside of the object (since you could change them at any time). While they’re not truely private, the naming convention will help you distinguish between the two as if they were. This is a common practice in Node, used within the Node code-base and by most other developers.
Antipattern: Truly Private Properties (At Whatever Cost)
I need to apologize now, because I lied in the above section: There is one other option for truely private variables. It involves being clever with scope. To define variables and functions that are truely private to each instance, you’ll need to define your entire module within the private scope of your constructor:
paid is individual to each instance, and correctly referenced each time
togglePaid() is called. But since it is never attached to the User object, it remains private within the constructor function.
Doing this way isn’t necessarily wrong, but it is slow. The constructor is run every time a new User is created, so the
togglePaid() function needs to be recreated for each new user instance. If you modify the prototype instead,
togglePaid() would only be defined one time, when the module is loaded. While performance may not always be a concern, this pattern should only be used if truly private variables are absolutely necessary.
Bringing it all Together
To see all these concepts come together, check out a full constructor design and all the other Node.js Cookbook examples. And remember, rules are meant to be broken. Use what works for you, discard what doesn’t, and you’ll be well on your way to Node Nirvana.