views:

278

answers:

4

I'm rewriting a JavaScript project, and I want to be able to use object oriented methodologies to organize the mess that the current code is. The main concern is that this JavaScript is supposed to run as a widget inside 3rd party websites and I can't have it conflicting with other JavaScript libraries that other websites may use.

So I'm looking for a way to write "class-like" inheritance in JavaScript that has the following requirements:

  1. No external libraries or things that would conflict with an external library (that precludes copy&paste from an external library).
  2. Minimalistic - I don't want the support code to be larger then a few lines of code and I don't want the developers to need a lot of boiler-plate every time they define a new class or methods.
  3. Should allow for dynamically extending parent objects so that child objects see the changes (prototype).
  4. Should allow for constructor chaining.
  5. Should allow for super type calls.
  6. Should still feel JavaScript-ish.

Initially I tried to work with simple prototype chaining:

function Shape(x,y) {
  this.x = x;
  this.y = y;

  this.draw = function() {
    throw new Error("Arbitrary shapes cannot be drawn");
  }
}

function Square(x,y,side) {
  this.x = x;
  this.y = y;
  this.side = side;

  this.draw = function() {
    gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); ...
  }
}
Square.prototype = new Shape();

And that solves requirements 1, 2 and 6 but id does not allow super calls (new functions override parent functions), constructor chaining and dynamically extending a parent does not provide the new methods to a child class.

Any suggestions will be welcome.

+3  A: 

Douglas Crockford has good articles on both classical and prototypal inheritance in Javascript, which should make good starting points.

Andrzej Doyle
I'm familiar with Crockford's articles, and I don't like them. He either requires a lot of boiler plate code (take a look at the first example of constructing an object in the your prototypal inheritance link) or requires several dozens of code lines for the "sugar". Or both. And the result does not look JavaScript-ish at all with all kinds of `uber` and `beget` stuff - the purpose is that a trained JavaScript developer can look at the code and immediately understand whats going on.
Guss
It's hard not to end up with some boiler plate, trying implement the functionality you want, though. And.. To be fair: Every trained JavaScript developer _should_ be familiar with Crockford's work :)
roosteronacid
+3  A: 

I'd suggest the following pattern which makes use of a clone function to inherit from the protoypes and not instances:

function Shape(x, y) {
    this.x = x;
    this.y = y;
}

Shape.prototype.draw = function() {
    throw new Error('Arbitrary shapes cannot be drawn');
};

function Square(x,y,side) {
    Shape.call(this, x, y); // call super constructor
    this.side = side;
}

// inherit from `Shape.prototype` and *not* an actual instance:
Square.prototype = clone(Shape.prototype);

// override `draw()` method
Square.prototype.draw = function() {
    gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ...
};

It's important that methods reside in the prototype (which is as it should be anyway for performance reasons) so you can call the methods of a super class via

SuperClass.prototype.aMethod.call(this, arg1, arg2);

With some syntactic sugar, you can make JS look like a classical class-based language:

var Shape = Class.extend({
    constructor : function(x, y) {
        this.x = x;
        this.y = y;
    },
    draw : function() {
        throw new Error('Arbitrary shapes cannot be drawn');
    }
});

var Square = Shape.extend({
    constructor : function(x, y, side) {
        Shape.call(this, x, y);
        this.side = side
    },
    draw : function() {
        gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ...
    }
});
Christoph
PS: instead of a custom `clone()` function, you can use `Object.create()` if your implementation support ES5
Christoph
I like it, although I'm probably not going to implement the syntactic sugar thing. One question though - why use clone() and not inherit from an instance? it looks to me to work the same way but using clone() is less readable.
Guss
@Guss: in my opinion, using instances for inheritance is bad practice because it will run the super class' constructor; depending on what the constructor actually does, this can have undesired side-effects; calling the super class' constructor explicitly in the child class' constructor is more clean as this will run the initialization code only when you actually want to create a new instance
Christoph
@Guss: I also don't use the sugar code myself, but it was still fun to code ;)
Christoph
Granted - that is a good excuse. Thanks for the explanation!
Guss
@Christoph, thanks for the help. I've reformatted clone() as an `Object` method and now it looks like this: http://geek.co.il/articles/javascript-class-inheritance.html . I'm still not 100% happy with the `super` call, but its doable.
Guss
Christoph's solution is essentially what Zakas calls "Parasitic Combination Inheritance" (pages 179-181 of his JavaScript book). Note I'm not affiliated ;)Oh, and why not inherit from instance? Christoph is right, but specifically you have to call constructor twice! 1) Constructor Steal call 2) Proto assignment. His "clone" is called inheritPrototype(SubType, SuperType) in the Zakas examples.
Rob
Essentially: inheritProto(sub,sup) {var proto = createObjectHelper(sup.prototype);prot.constructor = sub;sub.prototype = proto;}
Rob
And I have working code examples of pretty much ALL popular inheritance patterns here: http://github.com/roblevintennis/Testing-and-Debugging-JavaScript
Rob
A: 

Also Crockford-inspired, but I had good experiences with what he calls "functional inheritance" using "constructor functions". YMMV.

UPDATE: Sorry, I forgot: you still need to augment Object with a superior method to get nice access to a super method. Not a good fit for you, probably.

var makeShape = function (x, y) {
    that = {};
    that.x = x;
    that.y = y;
    that.draw = function() {
        throw new Error("Arbitrary shapes cannot be drawn");
    }
    return that;
};

var makeSquare = function (x, y, side) {
    that = makeShape(x, y);
    that.side = side;
    that.draw = function() {
        gotoXY(that.x,that.y); lineTo(that.x+that.side, that.y); ...
    }
    return that;
};
Fabian Neumann
Too much boilerplate, but thanks for trying :-)
Guss
+1  A: 

OK, the trick with reproducing a class/instance-style system in JavaScript is that you can only use prototype inheritance on the instances. So you need to be able to make a ‘non-instance’ instance that is only used for inheritance, and have an initialiser method separate from the constructor function itself.

This is the minimal system I use (before adding frills), passing a special one-off value into the constructor to have it construct an object without initialising it:

Function.prototype.subclass= function() {
    var c= new Function(
        'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+
        'if (arguments[0]!==Function.prototype.subclass._FLAG && this._init) this._init.apply(this, arguments); '
    );
    if (this!==Object)
        c.prototype= new this(Function.prototype.subclass._FLAG);
    return c;
};
Function.prototype.subclass._FLAG= {};

The use of new Function() is a way to avoid forming an unnecessary closure over subclass(). You can replace it with a prettier function() {...} expression if you prefer.

Usage is comparatively clean, and generally like Python-style objects only with slightly clumsier syntax:

var Shape= Object.subclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};
Shape.prototype.draw= function() {
    throw new Error("Arbitrary shapes cannot be drawn");
};

var Square= Shape.subclass();
Square.prototype._init= function(x, y, side) {
    Shape.prototype._init.call(this, x, y);
    this.side= side;
};
Square.prototype.draw= function() {
    gotoXY(this.x, this.y);
    lineTo(this.x+this.side, this.y); // ...
};

Monkey-patching a builtin (Function) is a bit questionable, but makes it pleasant to read, and no-one's likely to want to for...in over a Function.

bobince
Why `Function` constructor?
kangax
`function() {...}` makes a closure over the scope of the function in which it's being used, leaving references to arguments and local variables in that function. `new Function()` avoids this. Though it doesn't really matter either way, as there'll be nothing heavyweight in the closure.
bobince
Yes of course. I'm just not sure why you used "heavy" `Function` where plain `function(){}` suffices.
kangax
`Function` is heavier to call, but the resulting object is lighter as it is missing a scope frame.
bobince