May 23rd, 2006

Prototype: Inheritance Madness

26 comments on 1678 words

UPDATE: October 9th, 2007 – This article is no longer relevant for 1.6+. Please see: http://prototypejs.org/learn/class-inheritance

Prototype has a philosophy that you either love or hate. There was once a time where Prototype modified Object.prototype, causing jaws to drop and eyes to bulge. Some were so turned off by the decision they decided to create their own Javascript libraries.

All that has since changed. Thanks to the persuasion of The Javascript MacGyver, Sam replaced the Object.prototype bits in favor of Object.extend and Object.inspect. With this decision, however Prototype’s inheritance scheme can be messy, and it’s certainly hard to read if you don’t spend time trying to really figure out what’s going on.

Inheritance as it was

When Object.prototype.extend was changed to Object.extend it caused problems. Before the change it was easier to understand what was going on:


var Shape = Class.create();
Shape.prototype = {
  initialize: function(options) {
    this.options = options;
    this.width = 100;
    this.height = 100;
  },

  draw: function() {
    console.log('drawing');
  }
}

var Square = Shape.extend({
  initialize: function(options) {
    this.options = options;
  }
});

This is easy enough to understand, however it still has it’s problems. You overwrite any methods in the superclass when a method of the child class has the same name. We’ll go on about how we can fix this later, but for now, lets look at how you extend objects in Prototype in its current state.

Inheritance as it is

There are a couple different ways you can do inheritance in Prototype, each having its own unique behavior.

Extending the prototype, not initializing the parent


var Square = Class.create();
Square.prototype = Object.extend(Shape.prototype, {
  initialize: function(options) {
    this.options = options;
  }
});

In this example, we’ve extended the Shape.prototype object directly. If you load this up in Firebug’s console and execute new Square({sides: 4}), then inspect the object you’ll see that we do indeed get the draw method and our options have been set. For those paying close attention, you’ll notice that we did not inherit the superclass’s width and height properties.

Prototype-Inheritance-1

We didn’t inherit these properties because the initialize method of the superclass was never called, we have overwritten it with our subclass. So we can conclude that this method of inheritance is only ideal in unique situations where we don’t have a need to initialize a parent class. There are other hackish ways to work around this. You could name the initialize method of the superclass to something like baseInitialize and call it in your sublcass constructor. It’s ugly, but it works. This also assumes you never need to instantiate the parent class directly.

Extending An Anonymous Instance

One way to get around the parent class initialization problem is to extend an anonymous instance of it. Change your Square code to this:


var Square = Class.create();
Square.prototype = Object.extend(new Shape(), {
  initialize: function(options) {
    this.foo = 'I am foo';
    this.options = Object.extend({
      color: 'red'
    }, options || {});
  }
});

If you load this up in your console and create a new Square you’ll see that this works pretty well. We’ve successfully initialized the Shape object and our Square object, and we have everything we’d expect to see in our instance.

So what’s wrong with this approach? The anonymous object gets created instantly when the page is loaded. So lets see where this approach backfires:


var Shape = Class.create();
Shape.prototype = {
  initialize: function(options) {
    this.options = options;
    this.width = 100;
    this.height = 100;

    this.draw();
  },

  draw: function() {
    console.log('drawing');
  }
}


var Square = Class.create();
Square.prototype = Object.extend(new Shape(), {
  initialize: function(options) {
    this.foo = 'I am foo';
    this.options = Object.extend({
      color: 'red'
    }, options || {});
  },

  draw: function() {
    console.log(this.options);
  }
});

When you create a new instance of Square here, which draw method do you think will be called? Since we’ve overwritten draw in our subclass, you would expect it to come from Square right? Lets see what happens when we load this code up in the browser.

Prototype Inheritance

The first line “drawing” is shown instantly. What gives though? We’ve overwritten this method right? Well yes and no. Because we created a new instance of Shape when we loaded the page, it knows nothing about our draw method in our subclass. It’s just hanging around, waiting to be acquainted with our subclass when we actually instantiate it.

So what happens when we instantiate our Square class? Since we are initializing the parent, we should get the correct draw method called right? I’m afraid it doesn’t work that way. The last two lines of the image above show what we get. The Shape class doesn’t reinitialize when we create an instance of Square, we are merely extending an instance of Square that was created when the page loaded.

Fixing the Problem

Trying to describe the quirks of inheritance with Prototype in my book on Prototype is a challenge to say the least. I don’t think it was ever Sam’s intentions to have this type of quirky inheritance scheme, but when Object.prototype.extend was changed, so did the rules.

There has got to be a better way to handle this. It turns out that the guy who broke the original way came up with a better way. Dean Edwards has came up with a beautiful way of handling Javascript inheritance with Base (which he seems to be updating at the time of this article…hows that for timing Dean) ;-).

At the moment I’ve resorted to using Base.js with Prototype and so far it has worked flawlessly. I’ve written a complete replacement for plotKit using it (more on this later). It makes adding new chart types and extending the library super easy.

Prototype needs a replacement for the the Class and Object.extend bits, and I personally feel that we need to integrate something like Base into Prototype before it gets to be too late. We can retain backwards compatibility and also provide a new, and much better way to work with inheritance.

Sam, accepting patches for this? ;-)

Discussion

  1. masukomi masukomi said on May 23rd

    a) can’t wait to see a prototype compatable version of PlotKit.

    b) your link to PlotKit is broken: http://http//www.liquidx.net/plotkit/ should be http://www.liquidx.net/plotkit/

  2. Dean Edwards Dean Edwards said on May 23rd

    The JavaScript Macgyver? I like it. :-)

    I am updating the development area of my Base project. This is mostly due to me accidently overwriting it with unfinished code. Doh! I’ll get the development area presentable again in a few days. In the meantime, the Base class is the only officially released code.

  3. Dean Edwards Dean Edwards said on May 23rd

    Cool commenting system BTW. ;-)

  4. Andrew Andrew said on May 23rd

    Ok, I wouldn’t do this except now you’re writing a book, so you’re fair game for grammer nits:

    ”...in Prototype in it’s current state” should be ”...in Prototype in its current state.”

    ”...each having it’s own unique behavior” should be ”...each having its own unique behavior”

    ”...you’ll see that this works pretty good” should be ”...you’ll see that this works pretty well”.

    “before it gets to be to late” should be “before it gets to be too late.”

    Yes, technical writers still need to get the basics of grammar right. :-)

  5. Andrew Dupont Andrew Dupont said on May 23rd

    I think that Base could be officially integrated with Prototype very, very easily. I’d only change Base to Class and the base method (neé inherit) to uber (in keeping with Ruby’s class/klass thing and in deference to Douglas Crockford).

    I feel like Sam is a groundhog, emerging only once every year or so to make a blog comment or some other public statement, after which he retreats into his hole once again. I can only assume that he’s writing code down there.

  6. Justin Palmer Justin Palmer said on May 23rd

    Grr at Typo and it’s inability to deal with quotes.

    @Andrew: Haha! I deserve that. I have editors for this type of stuff in the book. ;-)

    @Dean: I think it’s a perfectly suitable nickname, glad you liked it. ;-)

  7. Grant Grant said on May 23rd

    @Andrew: speaking of grammar nits :)

    Ok, I wouldn’t do this except now you’re writing a book, so you’re fair game for grammer nits:
  8. Sam Stephenson Sam Stephenson said on May 23rd

    I’ve written more about this on my new blog.

  9. Justin Palmer Justin Palmer said on May 23rd

    You’ve completely abolished your mystery and mystique by setting up a weblog. Now we can’t refer to you as illusive and mysterious. ;-)

  10. Dean Edwards Dean Edwards said on May 23rd

    @Andrew – I’m renaming “inherit” to “base”. I was the only one that liked “inherit”. :-(

    And, whoah, Sam has a blog! And it looks like he is seriously considering using Base for Prototype! :-)

  11. Dan Webb Dan Webb said on May 23rd

    Justin, that’s spot on. It’s pretty much word for word what the first part of my review of Prototype would have said :)

    I myself would love to see Base in Prototype.

  12. Justin Palmer Justin Palmer said on May 24th

    Thanks Dan.

    Continuing the conversation from Dustin’s site, you should indeed write your thoughts about it because I’m interested to know what beyond the inheritance parts of Prototype that you find troublesome.

  13. Kevin Le Kevin Le said on May 24th

    Justin,

    I’ve been using Prototype and what I like the most is not just Prototype itself(of course it’s great), but all these great libraries/frameworks/... such as Scriptaculous, etc which are Prototype-based. Then I heard about MochiKit, which seems to be very solid but unfortunately seems to take an orthogonal approach from Prototype, as you mentioned. Since MochiKit is designed to play well with others (I assume Prototype included), do you see any problem for using both P&M in the same app? Thanks

  14. Kevin Le Kevin Le said on May 24th

    Sorry if my comment above has little to do with the topic of the article, but it’s only a question related to the first paragraph of the article.

  15. Justin Palmer Justin Palmer said on May 24th

    Kevin,

    I’m not sure if Prototype plays well with Mochikit. Both of them are solid libraries and you would be duplicating a lot of functionality by using both of them.

    The scope of Mochikit is a lot broader though, so it’s heavier, but it has a lot of nice stuff (Color, DOM, etc) that Prototype doesn’t include.

    I guess my question would be why would you need both of them?

  16. Kevin Le Kevin Le said on May 24th

    Justin,

    Briefly reviewing the MochiKit docs, I can tell that MK.DOM, .Logging, .Date, .Time would be very useful. Especially, using MK.DOM can lead to very clean and maintainable code in the long run. I can live without the rest, but for me, I would like MK.DOM to complement Prototype. On the other hand, MK does not have facility to do effects like Scriptaculous. Ajax-wise, both are probably equal (but I’ll stick with Prototype’s Ajax objects), and as you said, having both libraries results in a lot of duplicates.

    What are your thoughts?

  17. Justin Palmer Justin Palmer said on May 24th

    If you really just need a lightweight DOM Builder then I would use Dan’s DomBuilder that can be found here

  18. Abdur-Rahman Advany Abdur-Rahman Advany said on May 29th

    Hi,

    Thanks for this insightfull article, very nice to see both sam and dean respond to it in a positive way.

    Can’t wait to see your implementation of plotkit :) Any idea when you are realising it?

  19. Justin Palmer Justin Palmer said on June 2nd

    Abdur: I hope to get it out next week sometime if all goes well. We are in the midst of a product launch at JadedPixel so I haven’t had time to get everything in order yet.

  20. Abdur-Rahman Advany Abdur-Rahman Advany said on June 3rd

    Wheeeha, can’t wait, lot of luck with shopify, will be a great success! no doubt :)

  21. Sam Kellett Sam Kellett said on June 7th

    I’ve always been disappointed with JavaScripts class structure, thanks for pointing out Dean’s Base.js!

  22. Thomas Frank Thomas Frank said on June 15th

    And here’s a different approach to classes, inheritance and public/private methods in JavaScript:

    http://www.thomasfrank.se/classy_json.html

  23. Jeroen Jeroen said on June 19th

    For people that are interested in a Java/C#-style OOP in JavaScript, I have created a small shareware library. You can download it at http://software.antrix.nl/

  24. Alex Arnell Alex Arnell said on June 21st

    I’ve just submitted a small prototype patch that adds OO support. It achieves everything in Dean Edwards’ list and

    • It doesn’t add any methods or properties to your class definitions to do it’s magic.
    • It keeps with the ruby feel of prototype by mimicking the way ruby does OO, including support for mixins.
    • It works with, and is backward compatible with prototype.

    Just in time for RailsConf, too bad I won’t be attending.

  25. Cory Hudson Cory Hudson said on July 15th

    I’ve been playing around with this, and after finding the first half of the solution in moo.fx, I think I’ve come up with the second half.

    var Shape = Class.create();
    Shape.prototype = {
      initialize: function(options) {
        this.options = options;
        this.width = 100;
        this.height = 100;
    
        this.draw();
      },
    
      draw: function() {
        console.log('drawing');
      }
    }
    
    var Square = Class.create();
    Object.extend(Object.extend(Square.prototype, Shape.prototype), {
      _parent: Shape.prototype,
    
      initialize: function(options) {
        options = Object.extend({
          color: 'red'
        }, options || {});
    
        this._parent.initialize.call(this, options);
    
        this.foo = 'I am foo';
      },
    
      draw: function() {
        console.log(this.options);
      }
    });
    
    

    I left the Shape class unchanged, but modified the Square class to inherit from the Shape class. Run the code shape = new Shape({sides: 3}) and square = new Square({sides: 4}) through Firebug, and you should find that each class has the correct properties and methods.

    Moo.fx uses the Object.extend(Object.extend(child.prorotype, parent.prototype), { }) method for some of its classes. This works by first copying all properties/methods of the parent’s prototype into the child’s prototype via the inner Object.extend call. The outer Object.extend call then extends the child’s prototype with the following object. In moo.fx, this is used to completely override certain methods of a parent’s class.

    The solution I stumbled upon today is to define a _parent property of the child’s prototype to point to the parent class’s prototype. With this solution, you can then call the parent class’s method inside the child’s method, then overwrite/write additional code after that. This works by using the line this._parent.{method_name}.call(this, [arg1 [, arg2]] inside the method you want to modify.

    In the Shape example, the Square’s initialize method first extends whatever options were passed in with {color: 'red'}, then calls the parent’s initialize method with this resolving to the Square, and passing in the options parameter. The draw function is completely overridden by the Square class.

    This method of inheritance reminds me of PHP’s parent::{method_name}() way of calling a parent’s method inside a child class.

  26. SEO SEO said on September 26th

    Jeroen: thanks the library, its realy useful

Sorry, comments are closed for this article.