JavaScript Prototypes versus Closures
Recently I've been coordinating and authoring some JavaScript best practice documentation, and one area that came up which I thought was interesting was the reason for using prototypes instead of constructors to define JavaScript class members. Thanks to Bertrand, Dave, and Stephen for assistance with this.
There are two main ways to create JavaScript class definitions, and I've included them in a code sample attached to this post.
Example A
The first approach is to use the JavaScript prototype keyword to create a "template" for future instances of this class;
(function(window) {
window.ExampleA = function ExampleA(name) {
this._name = name;
}
window.ExampleA.prototype = {
get_name: function get_name() {
return this._name;
},
set_name: function set_name(value) {
this._name = value;
},
printName: function printName() {
return 'My name is ' + this._name;
}
}
})(window);
In this example I've also wrapped the class definition in a function to give my function names local scope – check out my Closure Exposure post if this doesn't make sense to you.
Example B
The alternative is to define all the functions inside the class constructor, and to attach them to the "this" object;
// using a closure in the constructor
ExampleB = function ExampleB(name) {
this._name = name;
this.get_name = function get_name() {
return this._name;
}
this.set_name = function set_name(value) {
this._name = value;
}
this.printName = function printName() {
return 'My name is ' + this._name;
}
}
As the function name scope is within the constructor there's no need for a wrapping function to create a scope. Often variables inside the constructor are used by the defined functions, hence my reference to closures in this post.
So What?
You'll be interested to know that Example B performs worse than Example A. Open the attached code sample and test the timings; on my machine Example B is consistently at least twice as slow as Example A! If you click the second button it might become clear as to why...
On two instances of Example A, the function definitions (note: not the return value – the definition) for printName are equal. That is, they are the same function being called on two different instances.
On two instances of Example B, the function definitions are not equal. That is, these are two completely different functions existing on two different instances that happen to have the equivalent implementations. Owch!
Of course, if you think about this the memory implications for holding all the function definitions and the CPU involved in creating them are potentially quite significant, and hence the time taken to construct objects can also be slower (and in a loop, this can matter). This is why you'll see us preferring the prototype approach, and I'd recommend you do the same.