14

My terminology is a bit off, so feel free to correct where necessary. I want to overload a function in javascript and the 'base class' to make use of the overloaded method as well as the inherited class to access the base classes methods. So far, I came up with a (working) mix of jquery.extend() and object literals, but this doesn't look pretty. I was wondering if there's a better way of doing this (can use jquery).

var Base = new function(args, m) {
   $.extend(this, m);
   var self = this;
   this.bar = function() {
     self.foo();
   }
   this.foo = function() {
     self.blah();
   }
   this.dosomething = function() {}
};

var Child = function(arg1) {
   $.extend(this, new Base(args, {
     blah: function() {
       self.dosomething()
     }
   }));
}
orange
  • 7,755
  • 14
  • 75
  • 139
  • 1
    I suppose this is [a mandatory resource](http://www.crockford.com/javascript/inheritance.html) – Alexander Mar 09 '13 at 01:10
  • @Alexander , from that page: "I have been writing JavaScript for 8 years now, and I have never once found need to use an uber function. The super idea is fairly important in the classical pattern, but it appears to be unnecessary in the prototypal and functional patterns. I now see my early attempts to support the classical model in JavaScript as a mistake." – Benjamin Gruenbaum Mar 09 '13 at 01:30
  • @BenjaminGruenbaum - I'm getting the idea you and Alexander are in agreement with Crockford that classical inheritance is a bad fit for Javascript, no? – Jared Farrish Mar 09 '13 at 01:34
  • @JaredFarrish I think we are (In agreement), I was not arguing, I thought it would be nice to bring Crockford's conclusion from that page here so it will be more visible. It's probably the most important part of that page :) – Benjamin Gruenbaum Mar 09 '13 at 01:37
  • 1
    @BenjaminGruenbaum - Yeah, you'd figure he'd spotlight that bit at the top. *Wrrrd*. – Jared Farrish Mar 09 '13 at 01:39

2 Answers2

20

What you're looking for is a way to share functionality across objects. This is exactly the sort of thing the JavaScript prototypical inheritance model excels at.

There's no need to use jQuery or other libraries in order to accomplish this. Consider going with the language's way of doing things.

Prototypes

In JavaScript, objects have 'prototypes'. When JavaScript looks for a method in an object that does not have it, it looks for it up on the prototype 'chain'. So all you need to do is override that functionality at a lower level on that chain.

This is explained in detail in the tutorial about it on MDN.

Your specific case

If I want a Base and Child class, where the Base has a method that Child needs to override, all we need to do is assign it anywhere lower in that chain.

The order of look-up is

Child Object --> Child's prototype (a Base object) --> Base's prototype (an Object)

For example, let's say you have a class Base

function Base(){

}
Base.prototype.bar = function() {
     //bar logic here
     console.log("Hello");
};
Base.prototype.foo= function() {
     //foo logic here
};

Function Child(){

}

Child.prototype = new Base();

I'd like Child to implement Bar differently, in which case I can do

Child.prototype.bar = function(){
   console.log("World");
}

Which results in

var a = new Base();
a.bar(); //outputs "Hello" to the console
var b = new Child();
b.bar(); //outputs "World" to the console
         //The Base instance that is the prototype of b has the bar method changed above

Note On Abstract Classes in JavaScript

Two of the primary reasons abstract methods inheritance is used in languages that are based on classical inheritance (like Java) is Polymorphism and code sharing.

In JavaScript neither are a problem. Code sharing can be done using prototypical inheritance just as easily. Moreoever, you can take just about any function and run it in another context. For example, I can even call the bar method of a Child object on an empty array by doing b.bar.call([]).

As for polymorphism JavaScript is a dynamic language with duck typing. This means it looks at objects based on their ability and not the way they were declared. If several objects have a method called bar I would have no problem calling that method on each of them if they are in an array or other collection. In Java that would require a common interface,type or ancestor.

For these reasons, things like abstract classes don't play a big role in JavaScript.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 1
    *If several objects have a method called bar I would have no problem calling that method on each of them if they are in an array or other collection.* I think I follow this comment, but can you codesplain it? – Jared Farrish Mar 09 '13 at 01:49
  • jsFiddle's being persnickety in loading, so I haven't seen what you provided yet, but I was also hoping to see the alternatives you've mentioned in the classical sense (*common interface, type or anscestor*). You don't have to of course, but it seems salient, especially for those "burdened" in their classical knowledge. – Jared Farrish Mar 09 '13 at 02:01
  • I've added a jsbin as well (If jsfiddle doesn't work for you). For that same example, on Java, you'd need a `Barrable` interface, with a single method `bar`, each of those objects would need explicit classes (`Animal`, `Car` and `Kid` accordingly, each in its own `.java` file), those classes each would need an `implements Barrable` at their class definition. Alternatively, you could decide that a kid, a car and an animal all share an ancestor but that's probably less likely :) – Benjamin Gruenbaum Mar 09 '13 at 02:05
  • That sounds like PHP (except that PHP separates `Abstract` and `Interface` definitions into with or without methods). I can't say I have any problems per se with classical inheritance, but I like Javascript's prototypical directness. – Jared Farrish Mar 09 '13 at 02:09
  • How would this work if Base takes a parameter to be constructed? Could all this `.prototype.foo = new function() {}' stuff go into the `Function Child() {}' or does it have to be assigned outside? – orange Mar 10 '13 at 03:29
  • All elements of `Child` have the same prototype, which is an object of type `Base`. If the Base constructor takes a parameter in the constructor it would be shared across all objects of `Child` anyway. You can do emulate Java's `super` if you'd like by doing a `Base.call(this,whatever_parameter)` in the `Child` constructor but that's very rare in practice, and would mean something different (that its a property of `Child` and not `Base`). In practice these cases do not arise. Again, in practice JavaScript is OO but _classless_ . – Benjamin Gruenbaum Mar 10 '13 at 03:36
  • As for where to assign all the `.prototype.stuff` you can do it inside the constructor, however it would assign a _different_ function to each object. Note that JavaScript functions are much more powerful than Java functions since they have closures, i.e. you can have a variable in the scope of the constructor (declared with the `var` keyword) and functions declared in the constructor will have access to it even _after_ the constructor finished. Closures are awesome. – Benjamin Gruenbaum Mar 10 '13 at 03:38
  • Thanks Benjamin. That's very helpful. I've creates a jsfiddle to play with your suggestions (http://jsfiddle.net/bXA7q/4/). Is this along the lines of what you're talking about? Also, the fact that I have to call `c.prototype.foo()' instead of `c.foo()' is a bit annoying. Is there anything I can do about it? – orange Mar 10 '13 at 03:55
2

I recommend doing it the way CoffeeScript does it. You can put the first var declaration in a separate file to keep the code looking nice. As far as I know __extends is equivalent to $.extends

var __hasProp = {}.hasOwnProperty,
  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };


var Fruit = (function() {

  function Fruit() {
    console.log("New fruit");
  }

  return Fruit;

})();

var Apple = (function(_super) {

  __extends(Apple, _super);

  function Apple() {
    console.log("New apple");
    Apple.__super__.constructor.apply(this, arguments);
  }

  return Apple;

})(Fruit);

var apple = new Apple();

Or, if you can use CoffeeScript, it looks like this:

class Fruit
  constructor: ->
    console.log "New fruit"

class Apple extends Fruit
  constructor: ->
    console.log "New apple"
    super
Brigand
  • 84,529
  • 20
  • 165
  • 173