6

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.

Guss
  • 30,470
  • 17
  • 104
  • 128
  • you can call super methods. If you will define draw in Triangle you can get super draw by Triangle.prototype.draw – Eldar Djafarov Sep 10 '09 at 11:21
  • The problem is that the "super" method has no access to the current object's fields - `this` is the prototype and not the `this` of draw. I can do `Square.prototype.draw.apply(this, arguments)` but that is clanky and I normally don't like methods to call their container class by name (they should use `this` everywhere). – Guss Sep 10 '09 at 11:54
  • and what about this.constructor.prototype.draw.apply(this, arguments) – Eldar Djafarov Sep 10 '09 at 12:29
  • @Eldar: woon't work because overwriting the prototype will make `constructor` unusable; it's also not really less clanky – Christoph Sep 10 '09 at 12:36
  • 1) Square is a constructor Square.prototype its prototype. Overwriting prototype have nothing with constructor. [this.constructor] equals [Square]. 2) there is nothing clanky about that. It is prototypal inheritance and it works that way. You have Constructor, you have prototype. this.constructor.prototype.constructor.prototype...draw.apply(this, arguments) – Eldar Djafarov Sep 10 '09 at 12:45
  • @Eldar: `constructor` is a property of the prototype object; see http://joost.zeekat.nl/constructors-considered-mildly-confusing.html – Christoph Sep 10 '09 at 12:47
  • @Eldar, actually `this.constructor` in `Square` equals `Shape`, because of `Square.prototype = new Shape()` – Guss Sep 10 '09 at 13:01

6 Answers6

5

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
  • 164,997
  • 36
  • 182
  • 240
  • PS: instead of a custom `clone()` function, you can use `Object.create()` if your implementation support ES5 – Christoph Sep 10 '09 at 11:47
  • 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 Sep 10 '09 at 12:14
  • @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 Sep 10 '09 at 12:26
  • @Guss: I also don't use the sugar code myself, but it was still fun to code ;) – Christoph Sep 10 '09 at 12:28
  • @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 Sep 10 '09 at 16:16
  • 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 Oct 29 '09 at 05:10
  • Essentially: inheritProto(sub,sup) { var proto = createObjectHelper(sup.prototype); prot.constructor = sub; sub.prototype = proto; } – Rob Oct 29 '09 at 05:12
  • And I have working code examples of pretty much ALL popular inheritance patterns here: http://github.com/roblevintennis/Testing-and-Debugging-JavaScript – Rob Oct 29 '09 at 05:13
  • @Christoph wow, thats a good answer. Well, faced same problem here in 2017, and do not want use sugar too. But what is different between your `clone` function and just using something like `this.prototype = Shape;` instead? Pros/cons? Do I miss something? – DanilGholtsman Mar 09 '17 at 09:32
4

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

Andrzej Doyle
  • 102,507
  • 33
  • 189
  • 228
  • 3
    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 Sep 10 '09 at 10:58
  • 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 :) – cllpse Sep 10 '09 at 13:38
1

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
  • 528,062
  • 107
  • 651
  • 834
  • `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 Sep 11 '09 at 11:10
  • 1
    Yes of course. I'm just not sure why you used "heavy" `Function` where plain `function(){}` suffices. – kangax Sep 12 '09 at 19:06
  • `Function` is heavier to call, but the resulting object is lighter as it is missing a scope frame. – bobince Oct 02 '09 at 15:49
1

The most common pattern I found when researching this question is described on the Mozilla Developer Network. I've updated their example to include a call to a superclass method and to show the log in an alert message:

// Shape - superclass
function Shape() {
  this.x = 0;
  this.y = 0;
}

// superclass method
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  log += 'Shape moved.\n';
};

// Rectangle - subclass
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

// Override method
Rectangle.prototype.move = function(x, y) {
  Shape.prototype.move.call(this, x, y); // call superclass method
  log += 'Rectangle moved.\n';
}

var log = "";
var rect = new Rectangle();

log += ('Is rect an instance of Rectangle? ' + (rect instanceof Rectangle) + '\n'); // true
log += ('Is rect an instance of Shape? ' + (rect instanceof Shape) + '\n'); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
alert(log);
  1. In the five years since you asked this question, it seems like browser support for inheritance has improved, so I don't think you need an external library.
  2. This the most minimal technique I've seen, I don't know if you consider that too much boilerplate.
  3. It uses the prototype, as requested, so adding new methods to the parent should provide them to the child objects as well.
  4. You can see constructor chaining in the example.
  5. Super type calls are also in the example.
  6. I'm not sure if it feels JavaScript-ish, you'll have to decide for yourself.
Don Kirkby
  • 53,582
  • 27
  • 205
  • 286
  • Looks nice, and does feel JavaScript-ish, but quite a lot of boilerplate :-/ still, a good solution. – Guss Jun 09 '15 at 13:24
0

You can use functional pattern proposed by Crockford in his book "JavaScript the good parts". Idea is use closore to make private fields, and use previleged function to access to these fields. Here is one of solutions which meet your 6 requirements:

    var people = function (info) {
    var that = {};
    // private
    var name = info.name;
    var age = info.age;
    // getter and setter
    that.getName = function () {
        return name;
    };
    that.setName = function (aName) {
        name = aName;
    };
    that.getAge = function () {
        return age;
    };
    that.setAge = function (anAge) {
        age = anAge;
    };
    return that;
};

var student = function (info) {
    // super
    var that = people(info);
    // private
    var major = info.major;
    that.getMajor = function () {
        return major;
    };
    that.setMajor = function (aMajor) {
        major = aMajor;
    };
    return that;
};

var itStudent = function (info) {
    // super
    var that = student(info);
    var language = info.language;
    that.getLanguage = function () {
        return language;
    };
    that.setLanguage = function (aLanguage) {
        language = aLanguage;
    };
    return that;
};

var p = person({name : "Alex", age : 24});
console.debug(p.age); // undefined
console.debug(p.getAge()); // 24

var s = student({name : "Alex", age : 24, major : "IT"});
console.debug(s.getName()); // Alex
console.debug(s.getMajor()); // IT

var i = itStudent({name : "Alex", age : 24, major : "IT", language : "js"});
console.debug(i.language); // Undefined
console.debug(i.getName()); // Alex
console.debug(i.getMajor()); // IT
console.debug(i.getLanguage()); // js
Hoa Nguyen
  • 13,452
  • 11
  • 45
  • 44
  • I don't like the this functional syntax because on one hand it doesn't feel like object oriented code (hence detrimental when you try to get programmers trained in classical OO to work with you), and on the other hand you don't get the advantage of prototypical inheritance where you can use the mix-in pattern. – Guss Apr 23 '12 at 10:35
-1

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
  • 1,862
  • 13
  • 17