I think it boils down to the issue of performance. You mentioned that there is small performance degradation, but this really depends on scale of the application(2 sheep vs 1000 sheep). Prototypal inheritance should not be ignored and we can create an effective module pattern using a mix of functional and prototypal inheritance.
As mentioned in the post JS - Why use Prototype?,
one of the beauties of prototype is that you only need to initialize the prototypal members only once,
whereas members within the constructor are created for each instance.
In fact, you can access prototype directly without creating a new object.
Array.prototype.reverse.call([1,2,3,4]);
//=> [4,3,2,1]
function add() {
//convert arguments into array
var arr = Array.prototype.slice.call(arguments),
sum = 0;
for(var i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
add(1,2,3,4,5);
//=> 15
In your functions, there is extra overhead to create a completely new
Animal and sheep each time a constructor is invoked. Some members such as Animal.name are created with each instance, but we know
that Animal.name is static so it would be better to instantiate it once. Since your code implies that Animal.name
should be the same across all animals, it is easy to update Animal.name for all instance simply by updating Animal.prototype.name if we moved
it to the prototype.
Consider this
var animals = [];
for(var i = 0; i < 1000; i++) {
animals.push(new Animal());
}
Functional inheritance/Module Pattern
function Animal() {
return {
name : 'Generic',
updateName : function(name) {
this.name = name;
}
}
}
//update all animal names which should be the same
for(var i = 0;i < animals.length; i++) {
animals[i].updateName('NewName'); //1000 invocations !
}
vs. Prototype
Animal.prototype = {
name: 'Generic',
updateName : function(name) {
this.name = name
};
//update all animal names which should be the same
Animal.prototype.updateName('NewName'); //executed only once :)
As shown above with your currently module pattern we lose effeciency in
updating properties that should be in common to all members.
If you are concered about visibility, I would use the same modular method you are currently using to encapsulate private members but also use
priviledged members for accessing these members should they need to be reached. Priviledged members are public members that provide an interface to access private variables. Finally add common members to the prototype.
Of course going this route, you will need to keep track of this.
It is true that in your implementation there is
-
No needing to track the 'this' pointer via $.proxy(fn, this) in callbacks
-
No more var that = this, etc. with event handlers, etc. Whenever I see a 'this', I know it is context that is being passed into a callback, it is NOT something I am tracking to know my object instance.
, but you are are creating a very large object each time which will consume more memory in comparison to using some prototypal inheritance.
Event Delegation as Analogy
An analogy to gaining performance by using prototypes is improved performance by using event delegation when manipulating the DOM.Event Delegation in Javascript
Lets say you have a large grocery list.Yum.
<ul ="grocery-list">
<li>Broccoli</li>
<li>Milk</li>
<li>Cheese</li>
<li>Oreos</li>
<li>Carrots</li>
<li>Beef</li>
<li>Chicken</li>
<li>Ice Cream</li>
<li>Pizza</li>
<li>Apple Pie</li>
</ul>
Let's say that you want to log the item you click on.
One implementation would be to attach an event handler to every item(bad), but if our list is very long there will be a lot of events to manage.
var list = document.getElementById('grocery-list'),
groceries = list.getElementsByTagName('LI');
//bad esp. when there are too many list elements
for(var i = 0; i < groceries.length; i++) {
groceries[i].onclick = function() {
console.log(this.innerHTML);
}
}
Another implementation would be to attach one event handler to the parent(good) and have that one parent handle all the clicks.
As you can see this is similar to using a prototype for common functionality and significantly improves performance
//one event handler to manage child elements
list.onclick = function(e) {
var target = e.target || e.srcElement;
if(target.tagName = 'LI') {
console.log(target.innerHTML);
}
}
Rewrite using Combination of Functional/Prototypal Inheritance
I think the combination of functional/prototypal inheritance can be written in an easy understandable manner.
I have rewritten your code using the techniques described above.
var Animal = function () {
var helloCount = 0;
var self = this;
//priviledge methods
this.AnimalHello = function() {
helloCount++;
console.log(self.Name + ' says hello (animalHello)');
};
this.GetHelloCount = function (callback) {
callback.call(null, helloCount);
}
};
Animal.prototype = {
Name: 'Generic',
IsAnimal: true
};
var Sheep = function (name) {
var sheep = new Animal();
//use parasitic inheritance to extend sheep
//http://www.crockford.com/javascript/inheritance.html
sheep.Name = name || 'Woolie'
sheep.SheepHello = function() {
this.AnimalHello();
var self = this;
this.GetHelloCount(function(count) {
console.log('i (' + self.Name + ') have said hello ' + count + ' times (sheepHello anon callback)');
});
}
return sheep;
};
Sheep.prototype = new Animal();
Sheep.prototype.isSheep = true;
var sheepie = new Sheep('Sheepie');
var lambie = new Sheep('Lambie');
sheepie.AnimalHello();
sheepie.SheepHello();
lambie.SheepHello();
Conclusion
The takeaway is to use both prototypal and functional inheritance to their advantages both to tackle performance and visibility issues.
Lastly, if you are working on a small JavaScript applications and these performance issues are not a concern,
then your method would be viable approach.