A Simple Javascript Library Design
When building a JavaScript library, you may find yourself on some code architecture issues. Should I use a constructor function, maybe with a prototype inheritance, isolating in a module or a mix of all of this? A library should have a well-defined public interface and protect itself from a carelessly programmer. In this post, I’m going to build a super simple library to illustrate some code designs.
The library has one purpose: to create Person objects. A Person object has a name, which is assigned in the person constructor and cannot be changed after the person was created. A Person object also have a me function that describes the person himself.
Some approaches are presented below as well as their pros and cons.
1 - Constructor function
function Person( _name ) {
this.name = _name;
this.me = function() {
return "Hi, my name is " + this.name;
};
}
var bernardo = new Person("Bernardo");
console.log( bernardo.me() ); // Hi, my name is BernardoCons:
-
the
nameis not a private variable; -
the
mefunction is created for each new objectPerson.
2 - Constructor function and prototype
function Person( _name ) {
this.name = _name;
}
Person.prototype = {
me: function() {
return "Hi, my name is " + this.name;
}
};
var bernardo = new Person("Bernardo");
console.log( bernardo.me() ); // Hi, my name is BernardoPros:
- the
mefunction is created only once. Each newPersonobject inherits themefunction through prototype.
Cons:
- the
nameis not a private variable.
3 - Revealing Module Pattern
function person( _name ) {
var name = _name;
return {
me: function() {
return "Hi, my name is " + name;
}
};
}
var bernardo = person("Bernardo");
console.log( bernardo.me() ); // Hi, my name is BernardoPros:
- the
nameis a private variable.
Cons:
- the
mefunction is created for each new objectperson.
4 - A Different Module Flavor
var Person = (function() {
var name,
module;
function InnerPerson( _name ) {
name = _name;
}
InnerPerson.prototype = {
constructor: InnerPerson,
me: function() {
return "Hi, my name is " + name;
}
};
return InnerPerson;
}());
var bernardo = new Person("Bernardo");
var foo = new Person("Foo");
console.log( bernardo.me() ); // Hi, my name is Foo
console.log( foo.me() ); // Hi, my name is FooPros:
- the
mefunction is created only once. Each newPersonobject inherits themefunction through prototype.
Cons:
- although the
nameis a private variable, it is shared by allPersonobjects. Therefore, thefooperson changes thebernardoperson name.
5 - Constructor function, prototype and getter function
function Person( _name ) {
var name = _name;
this.getName = function() {
return name;
};
}
Person.prototype = {
me: function() {
return "Hi, my name is " + this.getName();
}
};
var bernardo = new Person("Bernardo");
console.log( bernardo.me() ); // Hi, my name is BernardoPros:
-
the
nameis a private variable; -
the
mefunction is created only once. Each newPersonobject inherits themefunction through prototype.
Cons:
- the
getNamefunction should not be public because it is used only internally.
6 - Bind to solve
function person( _name ) {
function InnerPerson( _name ) {
this.name = _name;
}
InnerPerson.prototype = {
me: function() {
return "Hi, my name is " + this.name;
}
};
var innerPerson = new InnerPerson( _name );
return {
me: innerPerson.me.bind( innerPerson )
};
}
var bernardo = person("Bernardo");
console.log( bernardo.me() ); // Hi, my name is BernardoPros:
-
the
nameis a private variable; -
the
mefunction is created only once. Each newPersonobject inherits themefunction through prototype.
Cons:
- None, this is my preferred pattern.
If the return is this way:
return {
me: innerPerson.me
};It will not work because the this in the me function references a anonymous object. You must bind the me function to right object in the return statement.