January 8, 2014 (Updated December 18, 2014)
Node’s module system was built to share objects across an application. The singleton pattern is a natural fit for this, but what if you want something that isn’t shared? What if you want to create multiple objects, each with unique properties but shared common behavior? In most languages this would require a new class. In JavaScript, you’ll need something a little different.
JavaScript has no real idea of classes, at least not in the traditional sense. What it does have are types, which are so commonly used for the same purpose that the two are nearly synonymous. In fact, most developers would call the following definition a class, even thought ‘User’ is really just a new type of object.
function User(n) {
this.name = n;
}
var bob = new User('Bob');
While this pattern enables a lot of classy (ha!) things in JavaScript, User
is still just a function, and bob
is still just an object. But because JavaScript objects are so powerful, you can use them to create new custom types with unique methods/properties that make them feel and behave just like traditional classes.
User.prototype.sayHi = function() {
console.log('Hi, My name is ' + this.name);
};
bob.sayHi(); // "Hi, My name is Bob"
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.
//Private
var privateVariable = true;
//Public
module.exports = User;
function User(n) {
this.name = n;
}
User.prototype.foobar = // ...
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.
var User = require('User');
var bob = new User('Bob');
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:
var paid = true;
User.prototype.togglePaid = function togglePaid() {
paid = !paid;
}
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:
function User(n) {
this.name = n; // public
this._paid = true; // private
}
User.prototype.togglePaid = function() {
this._paid = !this._paid;
}
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.
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:
function User(n) {
var paid = true; // private (to each User instance)
this.name = n; // public
this.togglePaid = function togglePaid() {
paid = !paid;
}
}
Now 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.
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.