5

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?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
evanb
  • 53
  • 3
  • related: [What is the reason \[not\] to use the 'new' keyword here?](http://stackoverflow.com/questions/12592913/what-is-the-reason-to-use-the-new-keyword-here) – Bergi Jun 08 '14 at 12:54

4 Answers4

3

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.

Community
  • 1
  • 1
bobince
  • 528,062
  • 107
  • 651
  • 834
  • Thank you, this is very useful to know! It's gonna take a bit to fully sink in though... – evanb Feb 07 '10 at 18:06
3

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
  • 187,081
  • 35
  • 232
  • 306
  • 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 Feb 06 '10 at 13:24
  • @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 Feb 06 '10 at 13:36
  • 2
    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 Feb 06 '10 at 13:42
  • 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 Feb 07 '10 at 17:39
0

Here is an alternative way of having a deeper inheritance model:

//The functions we are going to use 
function A() {}
function B() {}
function C() {}
function D() {}
function E() {}

//set up the inheritance chain (order is important!)
D.prototype = new E();
C.prototype = new D();
B.prototype = new C();
A.prototype = new B();

//Add custom functions to each
A.prototype.foo = function() {
    console.log("a");
};
B.prototype.bar = function() {
    console.log("b");
};
C.prototype.baz = function() {
    console.log("c");
};
D.prototype.wee = function() {
    console.log("d");
};
E.prototype.woo = function() {
    console.log("e");
};

//Some tests
a = new A();
a.foo();
a.bar();
a.baz();
a.wee();
a.woo();

That gives you most of what you need. I've written a longer article about "nested inheritance in javascript" on my blog, which shows you how to define custom properties, static methods, static properties, etc.

Marcosc
  • 3,101
  • 4
  • 17
  • 16
0

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
  • 623,446
  • 136
  • 1,297
  • 1,155
  • 1
    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 Feb 06 '10 at 13:21
  • @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 Feb 06 '10 at 13:39
  • LOL Yeah. I, er, well, I *have* peeked inside jQuery. OMG. :-) – T.J. Crowder Feb 06 '10 at 13:54
  • Resig's approach seems interesting, but it's overkill for my purposes. Thanks for the pointer though! – evanb Feb 07 '10 at 17:59