views:

81

answers:

4

I've been trying to get my head around JavaScript inheritance. Confusingly, there seem to be many different approaches - Crockford presents quite a few of those, but can't quite grok his prose (or perhaps just fail to relate it to my particular scenario).

Here's an example of what I have so far:

// base class
var Item = function( type, name ) {
    this.type = type;
    this.name = name; // unused
};

// actual class (one of many related alternatives)
var Book = function( title, author ) {
    this.name = title; // redundant (base class)
    this.author = author;
};
Book.prototype = new Item('book'); // duplication of "book"

// instances
var book = new Book('Hello World', 'A. Noob');

This approach leaves me with a fair amount of redundancy, as I cannot delegate instance-specific attributes to the base class (at the time of prototype assignment, the attribute value is unknown). Thus each subclass has to repeat that attribute. Is there a recommended way to solve this?

Bonus question: Is there a reasonable way to avoid the "new" operator, or would that be regarded as a newbie working against the language?

A: 

I've never liked the base prototype approach either, John Resig (largest author of jQuery) has an alternative approach that I found much simpler, easier, and more natural looking coming from an OO background: Simple JavaScript Inheritance. Based on your comments (same thoughts I had) I'd really suggest taking a look.

If you don't like the style you're in, and it sounds like you're not that happy with it...change early. There are a few different options out there, there's rarely ever one way to do something in javascript.

Nick Craver
Any time Resig comes up with something, it's very worth looking at; he's a very smart and knowledgeable man. This particular inheritance implementation, though, has serious problems. It relies on function decompilation, which has never been standardized and if it were would be disallowed in the new "strict" mode, it involves creating a lot of extra closures (functions), and the use of it (`this._super(...)`) has too much magic in it for my taste (magic provided by the closures it adds around methods).
T.J. Crowder
@T.J. - I suggest you don't peek inside jQuery core then :) There's lot of magic going on (but, it's such great shiny highly optimized magic...)
Nick Craver
@Nick: LOL Yeah. I, er, well, I *have* peeked inside jQuery. OMG. :-)
T.J. Crowder
Resig's approach seems interesting, but it's overkill for my purposes. Thanks for the pointer though!
evanb
+1  A: 

The usual approach to this is to separate instantiation from initialization. That way, you can defer initialization to the point where your subclass has actual arguments to work with. Of course, the downside of doing that is that barring your putting controls around it, you can have instances with invalid states. One of the purposes of passing arguments to constructors, after all, is that sufficiently invalid arguments can be dealt with by throwing exceptions, preventing the caller from having a reference to an invalid instance.

Here, that would look something like this (but don't be put off, keep reading, there are ways to improve it):

// base class
var Item = function() {
    this.initialize.apply(this, arguments);
};
Item.prototype.initialize = function( type, name ) {
    this.type = type;
    this.name = name;
};

// actual class (one of many related alternatives)
var Book = function() {
    this.initialize.apply(this, arguments);
};
Book.prototype = new Item();
Book.prototype.initialize = function( title, author ) {
    Item.prototype.initialize.call(this, 'book', title);
    this.author = author;
};

// instances
var book = new Book('Hello World', 'A. Noob');

(Edit: The above was off-the-cuff; check out AnthonyWJones's answer for a much more terse version with a clever twist on initialization, although it doesn't demonstrate supercalls, which will still be as awkward as the above.)

As you can see, you end up doing a fair bit of typing to get all this done, and supercalls (calls to the superclass) are to be blunt fairly ugly. (Also, just using the arguments array in a function slows it down markedly in some implementations.) That's why you end up with helpers in libraries like Prototype, Resig's implementation, etc.

A number of those helper approaches, though, introduce a lot of inefficiency, use of never-standardized language features (Function#toString, for instance), and/or cause incompatibility with the new "strict" mode of JavaScript. I've written up a blog post showing how to do this rather more tersely whilst keeping supercalls efficient and strict-compliant.

Regarding doing away with new: I wouldn't, but I wouldn't call doing so being a newbie working against the language -- Crockford advocates not using it, for instance, and he's no newbie. To my mind, though, there's no reason to avoid it. The reasons people sometimes give are just around newbies making mistakes related to it, which you don't immediately seem likely to and which in any case just indicate that the newbies need to be mentored a bit.

T.J. Crowder
Thanks, this explanation is very useful as well. Too bad I can't accept more than one...
evanb
@evanb: No worries, glad Anthony's worked for you (I certainly leearned something from it). Hope the blog post is useful as well.
T.J. Crowder
+3  A: 

Is there a reasonable way to avoid the "new" operator

What you really want to do is create a JavaScript object that has Item.prototype in its prototype chain. But you don't want to call the Item constructor function, because that makes no sense: you're not instantiating Item, you're just making a subclass.

You do have to use new one way or the other to get that prototype chain, but you don't have to use Item to get the prototype. You can create a new non-constructor function with the same prototype but no action, which base classes can call when they don't actually want to instantiate an object:

function Item(type) {
    this.type= type;
};

function Book(title, author) {
    Item.call(this, 'book');
    this.name= title;
    this.author = author;
};
function Item_nonconstructor() {}
Item_nonconstructor.prototype= Item.prototype;
Book.prototype= new Item_nonconstructor();

You can factor this out to be a bit less ugly in a number of ways, for example:

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

function Book(title, author) {
    Item.call(this, 'book');
    this.name= title;
    this.author = author;
};
Book.subclass(Item);

Or, you could consider moving your constructor stuff to an initialiser function, or various other approaches. There's no single accepted class/instance system in JavaScript. See this question for a long discussion of JavaScript object models.

bobince
Thank you, this is very useful to know! It's gonna take a bit to fully sink in though...
evanb
+3  A: 

I'll show you how I achieve this sort of thing:-

 function Item(type, name)
 {
    if (arguments.length > 0)
    {
        this.type = type;
        this.name = name;
    }
 }

 function Book(title, author)
 {
   Item.call(this, "book", title);
   this.author = author;
 }
 Book.prototype = new Item();

So a couple of things I'm doing here, I skip some initialisation code in the base class when I detect a new instance is being created simply as prototype.

The real enabler that avoids your duplication is to use Item.call as a base class constructor. This avoids the duplication you have in the original.

As to avoiding new it would help if you indicate why you would want to? However a simple way is to add a function to the "Class function" directly rather than to the prototype of the function:-

Book.Create = function (title, author) { return new Book(title, author); }


var aBook = Book.Create("Code Complete 2", "Steve McConnell");

Although I see little gain here.

AnthonyWJones
That's *very* interesting, using `Item` both as a constructor and as an initialization function. Clever. Why the `if (arguments.length > 0)`, though? That will markedly slow down the function in many JavaScript implementations and I'm not immediately seeing any need for it.
T.J. Crowder
@T.J.: It avoids creating the `type` and `name` identifiers on the prototype object assigned to the sub-classes. There is also some assumption here (and its certainly true in cases of my code) where this code does a lot more than simply assign a bunch properties, in many cases such code would break or have undesirable side-effects if allowed to run when a _concrete_ instance is not actually being created. Think of such a situation as having an effective base class that doesn't have default constructor. However you're correct for performance reasons it may be more better not have this test.
AnthonyWJones
@Anthony: Thanks for that. I see your point. Perhaps rather than checking `arguments.length` one might explicitly support telling the constructor it's being used to build a prototype, for instance having a unique object (`var CONSTRUCT = {};`) and then passing it as the first argument. The check would then be `if (firstArgName !== CONSTRUCT) { ... }`. But we're getting into implementation details. I **really** like your trick, it's going in my bag. :-)
T.J. Crowder
@T.J. +1 Thats an excellent idea!
AnthonyWJones
This is exactly what I needed - many thanks! I don't really need to get rid of the new operator, it just seems a little redundant - but if it's good practice to use it, that's what I shall do.
evanb