2

I have a method in a base class that I want to keep in a subclass, but just add to it. I've found lots of stuff on augmenting classes and objects with properties and methods, but I can't find, or don't understand, how to just augment the method. The worst case scenario is that I would have to paste the entire method of the parent class into the subclass, but that seems like duplicate code... please help

function someObject (){
    this.someProperty = 1;
    this.incrementProperty = function incrementProperty(){   
        this.propertyOfSomeObject += 1;
    }
}

function newObject (){
    someObject.call(this);
    this.incrementProperty =  function incrementProperty(){
        //do everything the super class has for this property already
        return this.someProperty;
    }
}

var incrementer = new newObject;
alert (incrementer.incrementProperty()); //I want output to be 2
jgrant
  • 589
  • 1
  • 4
  • 13
  • 2
    Your `someObject.call(this)` is useless because you're overwriting the `incrementProperty` method that was assigned in the `someObject()` call with a different one on the next line. So there is no such "super" function to call. It has been deleted. – Blue Skies Dec 06 '13 at 02:06
  • so if I were to write var inc2 = new someObject; alert (inc2.incrementProperty()); it would not run this.propertyOfSomeObject += 1; ? – jgrant Dec 06 '13 at 02:18
  • Sure it would, but that's because you're getting the object from `someObject()` directly. That's not what your question is doing. In your question, you're getting `newObject()` then applying the `someObject()` function to the same object. So first the object gets the `incrementProperty` from `someObject()`, but then that one gets overwritten with the one from `newObject()` – Blue Skies Dec 06 '13 at 02:25
  • Oh, yeah. That's my question. How do I augment the method without overwriting it... or how do I call the superclass method while overwritting it right where I put "//do everything the super class has for this property already" ... You know? so I don't have to copy and paste this.propertyOfSomeObject +=1 into the new overwriting method. I'm trying to reuse that code. I guess I'm not really sure how to describe what I'm trying to do accurately. I hope this is clarifying. – jgrant Dec 06 '13 at 02:30
  • Is there a particular reason you're assigning the method to the object inside the constructor function? That's not a common approach, but is sometimes needed in very specific circumstances. – Blue Skies Dec 06 '13 at 02:34
  • No, I could do it outside. I'm just new to javascript and that looks normal to me from using java – jgrant Dec 06 '13 at 02:39
  • 1
    Oh, ok. No this will be entirely different from JavaScript. They use a constructor function because they wanted it to be Java-like when they invented it, but the underlying concept is totally different. Basically prototypal inheritance is a lookup chain of objects, and a constructor function sets up the relationship between two objects. So your `this` object is actually inheriting from the object located at `newObject.prototype`. So any method you add to `newObject.prototype` will be accessible from the object you created. I'd suggest looking at a tutorial or two. – Blue Skies Dec 06 '13 at 02:44
  • 1
    I second @BlueSkies. You should probably learn the basics of js prototypes before moving on, or else you'll be quite lost. Especially if you're coming from a Java background. The syntax can be very misleading. – linstantnoodles Dec 06 '13 at 02:46
  • Here's another question for you. Do you really need the methods on the two "classes" (I use that term loosely) to have the same name? In JavaScript, that actually makes things more difficult. There's no implicit relationship that will invoke the same method up the chain. So having the same name, the one gets in the way of the other. It's doable, but much more clunky. It's much easier if they have different names. – Blue Skies Dec 06 '13 at 02:49
  • Yeah, I'm working on tutorials and research. I finished two and have 21 tabs open right now in my browser trying to figure this out. Some specific tutorial would be more helpful. I'm working with html5 and the 2d context. I have a rectangle and a button and they both have a "draw" method. The button is a child of a rectangle. It draws a rectangle and it draws text inside of the rectangle. I just want the draw() method of the button object to know to draw the rectangle, and then add the other stuff I want to draw, you know? – jgrant Dec 06 '13 at 03:05
  • I want the draw method of the rectangle to remain untouched, and I don't want to have to call button.drawRectangle() and button.drawText() and button.drawWhateverElseIsAPartOfTheButton() every time the button is drawn. I just want to call button.draw() – jgrant Dec 06 '13 at 03:07
  • Then the answer from Andy Jones below would work. The `.prototype` of the `Button` constructor should be an instance of the `Rectangle` constructor, and define the `draw` methods on the `.prototype` of their respective constructors. Andy has a `.parent` property that would point to `Rectangle.prototype`, so inside the `Button.prototype.draw` method, you would call `this.parent.draw.call(this);`. Another way is to skip the `.parent` property, and just do `Rectangle.prototype.draw.call(this)` from the `Button.prototype.draw` method. – Blue Skies Dec 06 '13 at 03:27

4 Answers4

4
// parent object
function someObject () {
    this.someProperty = 1;
}

// add incrementProperty to the prototype so you're not creating a new function
// every time you instantiate the object
someObject.prototype.incrementProperty = function() {   
  this.someProperty += 1;
  return this.someProperty;
}

// child object
function newObject () {
    // we could do useful work here
}

// setup new object as a child class of someObject
newObject.prototype = new someObject();
// this allows us to use "parent" to call someObject's functions
newObject.prototype.parent = someObject.prototype;
// make sure the constructor points to the right place (not someObject)
newObject.constructor = newObject;

newObject.prototype.incrementProperty = function() {
    // do everything the super class has for this property already
    this.parent.incrementProperty.call(this);
    return this.someProperty;
}

var incrementer = new newObject();
alert (incrementer.incrementProperty()); // I want output to be 2

See: http://jsfiddle.net/J7RhA/

linstantnoodles
  • 4,350
  • 1
  • 22
  • 24
Andy Jones
  • 6,205
  • 4
  • 31
  • 47
  • This is the answer. Thank you SO much! I especially liked the jsfiddle link - very nice addition. I do have a couple follow up questions. 1 - the newObject.prototype.parent definition is a pointer to the someObject class right? So if the someObject class is changed dynamically after newObject inherits from it, the newObject class would not be changed, but references to newObject.prototype.parent would be changed? Also, I deleted the explicit newObject.constructor definition, and poked around, and it looked like it pointing to the right constructor - is that line really necessary? – jgrant Dec 07 '13 at 03:58
  • I ended up running into this problem using this method of inheriting: http://stackoverflow.com/questions/20463145/maximum-call-stack-size-exceeded-no-apparent-recursion – jgrant Dec 10 '13 at 05:31
1

this should do, you have to use prototype to have a real concept of oo with javascript

function someObject (){
    this.someProperty = 1;
    this.propertyOfSomeObject = 0;
    this.incrementProperty = function incrementProperty(){   
        this.propertyOfSomeObject += 1;
        return this.propertyOfSomeObject;
    }
}

function newObject (){
    someObject.call(this);
    this.incrementProperty =  function incrementProperty(){
        this.__super__.incrementProperty.apply(this);
        return this.propertyOfSomeObject + 1;
    }
}

newObject.prototype = new someObject()
newObject.prototype.__super__ = newObject.prototype

var incrementer = new newObject();
alert(incrementer.incrementProperty()); //I want output to be 2

experiment removing incrementProperty from newObject and it will return 1

alexgirao
  • 885
  • 9
  • 9
  • 2
    That isn't how you assign methods to the prototype. You're just putting a property named prototype directly on the object. I mean it sort of works, but it has nothing to do with prototypal inheritance. You could use `foo` and it would be the same. – Blue Skies Dec 06 '13 at 02:22
  • do you mean that `.prototype` behave the same as `.foo`? – alexgirao Dec 06 '13 at 02:26
  • Correct. The `.prototype` property with respect to OO in JavaScript is on the function that creates the object. Not on the object itself. So `someObject()` and `newObject()` both have a `.prototype` property that can be assigned methods. Those methods are then inherited by objects created from those functions. – Blue Skies Dec 06 '13 at 02:27
  • I think the right way to do that would be newObject.prototype.incrementProperty = function (){this.someProperty += 1;} ... but even if I did that, in your example, that code is repeated in both classes, so it's not reusing the code, which is what I'm trying to do. – jgrant Dec 06 '13 at 02:38
  • I updated the code, I missed the point that the prototype is a property of the function, not the object – alexgirao Dec 06 '13 at 02:39
  • This is getting better, but it's a bit confusing that the `__super__` of `newObject.prototype` is `newObject.prototype`. – Blue Skies Dec 06 '13 at 02:51
  • 1
    I don't particularly like this example because it's confusing. First, it stores the function as a property of the object (which make it seem like it's not really shared), and the base object gets set as the prototype (which is also kind of confusing because there's actually nothing on the base classes prototype, hence nothing to "inherit"..). – linstantnoodles Dec 06 '13 at 03:13
1

I usually use the augment library to write classes in JavaScript. This is how I would rewrite your code using augment:

var Foo = Object.augment(function () {
    this.constructor = function () {
        this.someProperty = 1;
    };

    this.incrementProperty = function () {
        this.someProperty++;
    };
});

var Bar = Foo.augment(function (base) {
    this.constructor = function () {
        base.constructor.call(this);
    };

    this.incrementProperty = function () {
        base.incrementProperty.call(this);
        return this.someProperty;
    };
});

As you can see since Bar extends Foo it gets Foo.prototype as a parameter (which we call base). This allows you to easily call the base class constructor and incrementProperty functions. It also shows that the constructor itself is just another method defined on the prototype.

var bar = new Bar;

alert(bar.incrementProperty());

The output will be 2 as expected. See the demo for yourself: http://jsfiddle.net/47gmQ/

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
0

From this answer:

Overriding functions

Sometimes children need to extend parent functions.

You want the 'child' (=RussionMini) to do something extra. When RussionMini can call the Hamster code to do something and then do something extra you don't need to copy and paste Hamster code to RussionMini.

In the following example we assume that a Hamster can run 3km an hour but a Russion mini can only run half as fast. We can hard code 3/2 in RussionMini but if this value were to change we have multiple places in code where it needs changing. Here is how we use Hamster.prototype to get the parent (Hamster) speed.

// from goog.inherits in closure library
var inherits = function(childCtor, parentCtor) {
  function tempCtor() {};
  tempCtor.prototype = parentCtor.prototype;
  childCtor.prototype = new tempCtor();
  childCtor.prototype.constructor = childCtor;
};


var Hamster = function(name){
 if(name===undefined){
   throw new Error("Name cannot be undefined");
 }
 this.name=name;
}
Hamster.prototype.getSpeed=function(){
  return 3;
}
Hamster.prototype.run=function(){
  //Russionmini does not need to implement this function as
  //it will do exactly the same as it does for Hamster
  //But Russionmini does need to implement getSpeed as it
  //won't return the same as Hamster (see later in the code) 
  return "I am running at " + 
    this.getSpeed() + "km an hour.";
}

var RussionMini=function(name){
  Hamster.apply(this,arguments);
}
//call this before setting RussionMini prototypes
inherits(RussionMini,Hamster);

RussionMini.prototype.getSpeed=function(){
  return Hamster.prototype
    .getSpeed.call(this)/2;
}    

var betty=new RussionMini("Betty");
console.log(betty.run());//=I am running at 1.5km an hour.
Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160