This leads me to the most common approach which does correctly set up prototype chain but has a few problems:
function Animal(name) {
this.name = name;
}
// This style of setting the prototype to an object
// works for classes that inherit from Object.
Animal.prototype = {
sayMyName: function() {
console.log(this.getWordsToSay() + " " + this.name);
},
getWordsToSay: function() {
// Abstract
}
}
function Dog(name) {
// Call the parent's constructor
Animal.call(this, name);
}
// Setup the prototype chain mmm... calling
// the Animal without the required params?
Dog.prototype = new Animal();
Dog.prototype.getWordsToSay = function(){
return "Ruff Ruff";
}
var dog = new Dog("Lassie");
dog.sayMyName(); // Outputs Ruff Ruff Lassie
console.log(dog instanceof Animal); // true
console.log(dog.constructor); // Animal ???? That's not right
console.log("name" in Dog.prototype)// true, but undefined
Alright, what's going on?
- Dog.prototype now has a property called "name" that is set to undefined.
That wasn't intentional. I knew that call to Animal's constructor was funny. Though that won't cause a problem, because we add a "name" to the object in the constructor, it's not very elegant - dog (the instance) has a constructor property but it points to Animal,
that's just wrong
How can we fix that? Here's a first try
// This is a constructor that is used to setup inheritance without
// invoking the base's constructor. It does nothing, so it doesn't
// create properties on the prototype like our previous example did
function surrogateCtor() {}
function extend(base, sub) {
// Copy the prototype from the base to setup inheritance
surrogateCtor.prototype = base.prototype;
// Tricky huh?
sub.prototype = new surrogateCtor();
// Remember the constructor property was set wrong, let's fix it
sub.prototype.constructor = sub;
}
// Let's try this
function Animal(name) {
this.name = name;
}
Animal.prototype = {
sayMyName: function() {
console.log(this.getWordsToSay() + " " + this.name);
},
getWordsToSay: function() {
// Abstract
}
}
function Dog(name) {
// Call the parent's constructor
Animal.call(this, name);
}
// Setup the prototype chain the right way
extend(Animal, Dog);
Dog.prototype.getWordsToSay = function(){
return "Ruff Ruff";
}
var dog = new Dog("Lassie");
dog.sayMyName(); // Outputs Ruff Ruff Lassie
console.log(dog instanceof Animal); // true
console.log(dog.constructor); // Dog
console.log("name" in Dog.prototype)// false
Nice isn't it? Let's add some syntactic sugar to make it more user friendly.
- Add a reference to the base class so we don't have to hard code it
- Pass the object's prototype methods into the call
function surrogateCtor() {}
function extend(base, sub, methods) {
surrogateCtor.prototype = base.prototype;
sub.prototype = new surrogateCtor();
sub.prototype.constructor = sub;
// Add a reference to the parent's prototype
sub.base = base.prototype;
// Copy the methods passed in to the prototype
for (var name in methods) {
sub.prototype[name] = methods[name];
}
// so we can define the constructor inline
return sub;
}
// Use the same animal from above
function Dog(name) {
// Call the parent's constructor without hard coding the parent
Dog.base.constructor.call(this, name);
}
extend(Animal, Dog, {
getWordsToSay: function(){
return "Ruff Ruff";
}
});
One more step, I don't even like hard coding the name of the class in the methods in case I rename it, how can we fix that?
The solution is to wrap everything in a self executing function and create a reference to the constructor
Dog = (function(){
// $this refers to the constructor
var $this = function (name) {
// Look, no hardcoded reference to this class's name
$this.base.constructor.call(this, name);
};
extend(Animal, $this, {
getWordsToSay: function(){
return "Ruff Ruff";
}
});
return $this;
})();
With this final approach, renaming the class or changing its parent requires changing a single place (for each change).
Update
This article was written a while ago, when Object.create wasn't as well supported. The extend function could be simplified to be
function extend(base, sub, methods) {
sub.prototype = Object.create(base.prototype);
sub.prototype.constructor = sub;
sub.base = base.prototype;
// Copy the methods passed in to the prototype
for (var name in methods) {
sub.prototype[name] = methods[name];
}
// so we can define the constructor inline
return sub;
}