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; }