1

Although I have some working experience with jQuery and JavaScript I still find it difficult to understand prototypal inheritance. Hence I have started reading Stoyan Stefanov's book entitled "Object Oriented JavaScript". However I ran into problems while solving the following exercises from the book:

  1. Create an object called shape that has a type property and a getType method.
  2. Define a Triangle constructor function whose prototype is shape. Objects created with Triangle should have three own properties: a, b and c representing the sides of a triangle.
  3. Add a new method to the prototype called getPerimeter.

Test your implementation with this code:

var t = new Triangle(1, 2, 3);
t.constructor;                 // Triangle(a, b, c)    
shape.isPrototypeOf(t);        // true
t.getPerimeter();              // 6
t.getType();                   // "triangle"

I have tried to solve this problem with the following code:

shape = {
    type : "",
    getType: function(){
        return this.type;
    }
};

function Triangle(a, b, c) {
}

Triangle.prototype = shape;

However it does not seem to work as expected. How would you solve this problem? Please explain it in detail. I would really like to understand prototypal inheritance.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
viktor
  • 1,050
  • 1
  • 12
  • 26

5 Answers5

3

You don't do anything with the params passed to the constructor function, probably assuming that they are just assigned to the newly-created object. The problem is, they aren't.

You should write something like this...

var shape = {
  type: '',
  getType: function() { return this.type; }
};

function Triangle(a, b, c) {
  this.type = 'triangle';
  this.a = a;
  this.b = b;
  this.c = c;
}

Triangle.prototype = shape;
Triangle.prototype.getPerimeter = function() {
  return this.a + this.b + this.c;
};
Triangle.prototype.constructor = Triangle;

The point (why constructor is defined for prototype) is very simple: each Triangle object should know about its constructor function, but this property will be the same for each instance of Triangle. That's why it's placed it on Triangle.prototype instead.

raina77ow
  • 103,633
  • 15
  • 192
  • 229
  • 1
    t.constructor still returns function Object() { [native code] } instead of Triangle(a,b,c) – viktor Aug 28 '13 at 17:46
  • You mean Triangle.prototype.constructor = shape; However the code still not works as expected, t.constructor returns shape object not Triangle(a, b, c) – viktor Aug 28 '13 at 17:55
  • @viktor I mean what I wrote, actually. `shape` is a prototype object. `Triangle` is a constructor function. It's the same for all the objects created with `Triangle` function, so it's assigned to `Triangle.prototype` instead of each instance of `Triangle`. – raina77ow Aug 28 '13 at 17:57
  • Yes this does the trick, it did not occured to me that the constructor must be reset, however in this case the parameters of the function must be assigned after the var t; was defined and also the getPerimeter() method. Is this correct? Also in this case does Triangle still inherits from shape? – viktor Aug 28 '13 at 18:03
2

Something like this will work:

function Shape() {
    this.type = "shape";
    this.getType = function(){
        return this.type;
    }
}

function Triangle(a,b,c){
     this.type="triangle";
     this.a =a;
     this.b = b;
     this.c = c;
}

var shape = new Shape(); //follow the requirements a bit more literally :)
Triangle.prototype = shape;

Triangle.prototype.getPerimeter = function() {
  return this.a + this.b + this.c;
}

jsfiddle example: http://jsfiddle.net/TbR6q/1

Tangentially, this is an area where coffeescript is very nice and allows you to be much more clear/concise. This is the equivalent in Coffeescript.

class Shape 
  constructor: ->
    @type = "shape"
  getType : -> @type

class Triangle extends Shape
  constructor: (@a,@b,@c) ->
     @type="triangle"
  getPerimeter: () -> @a + @b + @c

http://jsfiddle.net/qGtmX/

Ben McCormick
  • 25,260
  • 12
  • 52
  • 71
  • Shape is an object not a function and t.constructor has to return Triangle(a,b,c); Also getPerimeter is added after the function Triangle has been created. – viktor Aug 28 '13 at 17:53
  • you're using shape as a constructor to create an object. You don't need to do it that way, but when doing inheritance like this it produces a more consistent notation. You can add getPerimeter after or during creating Triangle. if you add it after then you'll need to set the a,b,c variables as properties on the Triangle object. It does look like thats the intention since it mentions adding it to the prototype. – Ben McCormick Aug 28 '13 at 18:31
  • I understand what you say, but the requirements are: "Create an object called shape". getPerimeter is added after the creation of the Triangle. Discovering that I can make a function point to an object was something very new to me and I think it is better to do it this way for learning purposes. – viktor Aug 28 '13 at 18:35
  • sure. feel free to do it any way you want to :) I showed you one way that I felt was clear, and accomplishes what you were looking to do. I didn't name my object shape, and I built it with a constructor, but it is still an object with those properties on the prototype. If you don't like my answer/notation, you don't have to use it :) rainna77wow's answer is also correct and seems to follow the requirements more literally. But we're using the same concepts whether its a constructor, an object literal, or even my coffeescript example. And the concepts are the important point here anyway. – Ben McCormick Aug 28 '13 at 18:46
  • I agree with you, however I understood the how prototype works at a very crude level, I would rather do something more complicated and follow the exact requirements in order to deepen my understanding. I understand your point of you and you are right, but I was after something else. – viktor Aug 28 '13 at 19:05
1

You're on the right track. Your code is correct. You only need to add a few more lines of code:

shape = {
    type : "",
    getType: function () {
        return this.type;
    }
};

function Triangle(a, b, c) {
    this.type = "triangle";
    this.a = a;
    this.b = b;
    this.c = c;
}

Triangle.prototype = shape;

shape.getPerimeter = function () {
    return this.a + this.b + this.c;
};

To understand what's happening I suggest you read the following answers:

  1. Object Inheritance in JavaScript
  2. What are the downsides of defining functions on prototype this way?
  3. JavaScript inheritance and the constructor property
Community
  • 1
  • 1
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • nice approach, however getPerimeter will not return 6 if we run Triangle(1,2,3) – viktor Aug 28 '13 at 18:59
  • @viktor yes it will. He's attaching the function to shape, which is the same object that Triangle.prototype is pointing to. It works fine and is the same in the end as my answer or raina77ow's. Its also why I just put the function inside the Shape constructor originally. Since its the same. Fiddle: http://jsfiddle.net/TbR6q/2/ – Ben McCormick Aug 28 '13 at 19:13
0

For the sake of learning I would make it like this

function Shape(){
    this.type = 'Shape';
    this.getType = function()
    {
        return this.type;
    }
}

var shape = new Shape();

function Triangle(a, b ,c)
{
    this.type = 'triangle';
    this.arguments = arguments;
}

Triangle.prototype = shape;

var triangle = new Triangle(1, 2, 3);

triangle.getType();

Triangle.prototype.getParimeter = function()
{
    var perimeter = 0;
    for(i = 0; i < this.arguments.length; i++){
        perimeter = perimeter + this.arguments[i];
    }
    return perimeter;
}

console.log(triangle.getParimeter());
matewilk
  • 1,191
  • 1
  • 13
  • 27
0

This is one solution (explanations on comments):

shape = {
    type : "",
    getType: function(){
        return this.type;
    }
};

function Triangle(a,b,c){
    //This three variables are defined inside a closure, so in this case
    //only the getPermiter function can access them
    var A = a, B = b, C = c;

    //The new Triangle object is crafted in the lines below as usual
    this.type = "triangle";
    this.getPerimeter = function(){
        return A + B + C;
    }
}

//Here we set the triangle prototype to point the shape object.
//So every time we call the Triangle function with the "new" operator
//the __proto__ internal property of the newly created object will be
//the shape object.
Triangle.prototype = Object.create(shape);

//The problem is that the shape object doesn't have a constructor property,
//so the shape constructor is shape.__proto__.constructor, which is the
//Object function. 
//All this means that when we create a new object with the Triangle function the
//constructor property will be the Object function (shape.__proto__.constructor).
//To avoid this we must manually set the constructor to be Triangle.
Triangle.prototype.constructor = Triangle;

var t = new Triangle(1, 2, 3);
console.log(t.constructor === Triangle);   
console.log(shape.isPrototypeOf(t) === true);
console.log(t.getPerimeter() === 6);
console.log(t.getType() === "triangle");
BenMorel
  • 34,448
  • 50
  • 182
  • 322
Augusto Altman Quaranta
  • 1,526
  • 1
  • 21
  • 32
  • Why not use object.create and polyfil it for older browsers. You can set Triangle.prototype.constructor after that – HMR May 29 '14 at 02:31
  • First of all, in the question was specified to test the implementation against a code, and that code starts with the line "var t = new Triangle(1, 2, 3);". So it was implied that we must use a function to construct the object (because the new operator). I could create an object with Object.create, it's true, but there is no need to use the Triangle function. Nevertheless, suppose that i create a function "Triangle", perform the Object.create inside it and return the newly created object, i would have to set the Triangle.prototype.contructor to Triangle anyway, so the effects would be the same. – Augusto Altman Quaranta May 29 '14 at 16:10
  • You're missing the point; Triangle.prototype === shape, when I use shape for Square.prototpe and set Square.prototype.constructor=Square then it'll set constructor for Triangle to Square. Setting prototype like this isn't good since a Triangle is a shape but a shape isn't a Triangle. You should do: `Triangle.prototype = Object.create(shape);` – HMR May 30 '14 at 02:29
  • You are right, i didn't understood you. I corrected my response. – Augusto Altman Quaranta Jun 10 '14 at 13:11