2
\$\begingroup\$

I've been playing around with inheritance in JavaScript and I'm wondering if there are any drawbacks to this method which tries to emulate classical inheritance from C-based languages.

Function.prototype.extends = function (parentClass) {
    function temp () { this.constructor = this; };
    temp.prototype = new parentClass();

    this.prototype = new temp();
};

var Animal = function() {
    function Animal() {};

    Animal.prototype.walk = function () {
        return this.name + ' walks';
    };

    return Animal;
}();


var Cat = function () {
    function Cat (name) {
        this.name = name;
    };

    Cat.extends(Animal);

    Cat.prototype.meow = function() {
        return this.name + ' meows';
    };

    return Cat;
}();

var Chester = new Cat('Chester');

console.log(Chester.meow()); // Chester meows
console.log(Chester.walk()); // Chester walks

This is a crude example, but can anyone point out any glaring problems?

\$\endgroup\$
3
\$\begingroup\$

Hang on a tick... there is a problem here that, weirdly nobody has noticed. I will admit that this is hardly ever used, but still:
In a normal JS prototypal inheritance scenario, you would be able to pull stunts like these:

var Animal = (function()
{
    function Animal()
    {
        this.name = this.constructor.name;
    };
    Animal.prototype.walk = function()
    {
        return this.name + ' is walking';
    };
    return Animal;
}()):
var Cat = (function(parent)
{
    function Cat(name)
    {
        this.name = name || this.constructor.name;
    };
    Cat.prototype = new Animal;
    Cat.prototype.constructor = Cat;
    Cat.prototype.sleep = function()
    {
        return this.name + ' is sleeping';
    };
    return Cat;
}(Animal));
var garfield = new Cat('Garfield');
//code
var newSameClass = new garfield.constructor();

Whereas the extends method you have written causes infinite constructor recursion: you're declaring a new function, that uses the this keyword... but because it's being used with the new keyword, it creates a new instance of itself (temp).
In the constructor of temp, all it does is equate its prototype to its own instance... you create an object that is its own prototype and therefore effectively is not part of any chain (it keeps on pointing to itself) and for some bizarre reason, you expect this to keep track of the constructor? I think what you ought to write is this:

Function.prototype.extends = function(parentClass)
{
    var temp = this.prototype.constructor;
    this.prototype = new ParentClass;
    this.prototype.constructor = temp;
};

Which works just fine, and doesn't require some temp constructor/object at any point. Just try it with this simple example:

function Animal(){};
function Cow(){};
Cow.extends(Animal);
function Bird(){};
function Chicken(){};
Bird.extends(Animal);
Chicken.extends(Bird);
var dinner = new Chicken();
console.log(dinner instanceof Animal);//true -- sorry vegetarians
console.log(dinner instanceof Cow);//false -- no beef tonight
console.log(dinner instanceof Bird);//true -- mmm, poultry
console.log(dinner instanceof Chicken);//true -- roasted, probably

Which is, I take it, what you wanted.
PS: I would strongly advise you not to augment prototypes you don't own. By that I mean Object, Array, Function, Date and the like... Save for a few cases where the String prototype poses X-browser issues (String.trim wasn't implemented in IE8, for example).
You might encounter issues with other toolkits/libs, future updates, issues depending on the implementation (IE, FF and V8 deal with native prototypes differently in some cases)

All in all, it's best to leave them be... I see no reason why you shouldn't simply write a function, and not attach it to the prototype. Or, if needs must, do what ECMA is doing: all these createObject-like thingies go into one big bin: Object as in Object.getOwnPropertyNames() and Object.getPrototypeOf(). What's so difficult about:

function extend(child, parent)
{
    var prto, Object.getPrototypeOf(child),
        tmp = prto.constructor;
    prto = new parent;
    prto.constructor = tmp;
    return child;//<-- this might be handy
}
//usage:
var garfield = new (extends(Cat, Animal))('garfield');
\$\endgroup\$
  • \$\begingroup\$ Thanks again! Weirdly I had picked up on infinite constructor recursion and the solution is a carbon copy of your suggestion. The original method was also exactly the same as your extend(child, parent) method. This was just an experiment to emulate the syntactical sugar of C-based languages, or rather, a more intuitive way of writing this extends that. \$\endgroup\$ – Ian Brindley Jan 17 '14 at 15:42
  • 1
    \$\begingroup\$ @IanBrindley: You're welcome... if you don't mind my asking: since stack-exchange sites aim to be a reference for future users, googling a specific problem, you might want to reconsider which answer is up-voted/accepted here. Since I mention the infinite recursion, and suggest a different approach that, by your own admission, is what you've ended up using, then perhaps this answer should be the one marked as accepted? (PS: as for the answer to your other, PHP, question: I know I'm a bit of a rep-whore, but an up-vote for my efforts would be greatly appreciated ;-P) \$\endgroup\$ – Elias Van Ootegem Jan 19 '14 at 10:55
1
\$\begingroup\$

Observations:

  • You changed the Function prototype, which is generally considered bad practice.
  • The inheritance does work, instanceof checks work fine
  • I do not see how it emulates classical inheritance better than this:

    var Cat = function () {
    
      function Cat (name) {
        Animal.call(this); //This takes care of inheritance, classic JS
        this.name = name;
      };
      //Fix prototype
      Cat.prototype = new Animal();
      Cat.prototype.constructor = Cat;
    
      Cat.prototype.meow = function() {
        return this.name + ' meows';
      };
    
      return Cat;
    }();
    

Except that you are more explicit and less verbose in declaring the inheritance. I guess it is a matter of style at this point, I would stick with the old school.

\$\endgroup\$
  • \$\begingroup\$ This is not a complete example and it doesn't establish inheritance. Try Chester.walk() for example. \$\endgroup\$ – Wayne Burkett Jan 13 '14 at 19:15
  • \$\begingroup\$ Fixed, so I guess the question is, is that function saving three lines worth it? I would guess not. \$\endgroup\$ – konijn Jan 13 '14 at 19:23
  • \$\begingroup\$ @tomdemuyt Thanks for your answer. I appreciate that extending the Function prototype may be frowned upon, but I do feel that this method feels a lot more intuitive when writing. My only concern really was that it might cause havoc with the prototype chain. \$\endgroup\$ – Ian Brindley Jan 14 '14 at 11:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.