4

There is already numerous threads about prototypal inheritance in JavaScript, but this is not a copy due to laziness. I have literally read them all and have found almost as many different syntactic approaches as I have found answers, so it appears that I am not the only one that is confused on this subject!

Specifics:

My current approach looks like this.

var Person = function(name) {
    this.name = name;

    this.greeting = function() {
        alert("Greetings, I am " + name);
    }

}

var bob = new Person("bob");
bob.greeting();

var Woman = function(name) {
    Person.call(this, name);
    this.gender = "female";
}

Woman.prototype = Object.create(Person.prototype);

var brenda = new Woman("brenda");
brenda.greeting();

Person.prototype.eats = true;

alert(brenda.eats);

Having tested the code I have found it works perfectly - as far as I can tell - but I have been told that this is not the best approach and that I should define the constructor like this:

Woman.prototype.constructor = Woman;

And not use the Person.call method in my actual constructor method. Two things, having been told that I can't see an easy way to then pass in the parameters using the 2nd approach, and also, just why? What I'm doing seems to work fine.

Am I missing something?

Is there some cases where what I'm doing will give unpredictable errors?

Can anyone give a definitive 'correct' approach and the reason for it?

Sam Redway
  • 7,605
  • 2
  • 27
  • 41
  • `prototype.call`? Did you mean `Person.call`? – thefourtheye May 23 '15 at 10:48
  • I think... and I could be wrong here, that the call method belongs to Object.prototype and is inherited . So Person.call is a shorthand for Person.prototype.call() .. so that's what I ment. – Sam Redway May 23 '15 at 10:52
  • FYI, `Person.call` is not the same as `Person.prototype.call`. `Person.protoype` is not a function, it cannot be called. – Felix Kling May 23 '15 at 11:12
  • 1
    One last note: `greeting` should really be defined as `Person.prototype.greeting`, not inside the constructor. Otherwise you are not taking advantage of prototypes (sharing data amongst instances). – Felix Kling May 23 '15 at 11:38

2 Answers2

5

What you have is, I think, considered to be the best approach. The point you raise however is irrelevant:

Woman.prototype.constructor = Woman;

is not a replacement for Person.call(...). In fact, those two things have nothing in common, they serve different purposes:

  • Calling the parent constructor in the child constructor ensures that the child instance is correctly initialized. It's like calling super() in ES6 or other languages.

  • Assigning to constructor simply restores the original value of Women.prototype.constructor. If you did not do that, brenda.constructor would refer to Person. This doesn't have an impact on the internal workings of your inheritance, but other code that consumes your object might rely on constructor having the correct value. See also Advantages of setting the "constructor" Property in the "prototype".

So the answer to your question is: you should do both.

Sometimes you see the assignment taking place as part of the Object.create call. This is even more accurate, since it recreates the original characteristics of the property (like non-enumerability):

Woman.prototype = Object.create(Person.prototype, {
  constructor: {value: Woman, writable: true}
});

FWIW, I believe one of the reasons of introducing class in ES6 was to reduce the confusion around constructor functions and prototypes. That is not to say that you should not know about it, after all, class is more or less just syntactic sugar, but it just makes inheritance a bit easier:

class Woman extends Person { // almost like Woman.prototype = Object.create(Person.prototype)
  constructor(name) {
    super(name); // almost like Person.call, but enforced by the interpreter
    this.gender = "female";
  }
}
Community
  • 1
  • 1
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Felix, I have no doubt you are correct, but for completeness could you explain why I need to explicitly define the constructor please? To me it seems strange, because clearly when I use the new operator as in woman = new Woman() it is saying that Woman is a constructor. Is this then necessary for objects that inherit from the Woman class? – Sam Redway May 23 '15 at 11:03
  • Thank you Felix. One other question. As per the answer below, many sources suggest that rather than Woman.prototype = Object.create(Person.protype) - which I understand as making a deep copy of the relevant prototype - they say instead I should simply write Woman.prototype = new Person() ... is this effectively the same thing? – Sam Redway May 23 '15 at 11:08
  • 2
    It can be the same thing, but it's conceptually wrong. See my answer here: [Benefits of using `Object.create` for inheritance](http://stackoverflow.com/q/17392857). – Felix Kling May 23 '15 at 11:10
  • 1
    Also, `Object.create` doesn't deep copy. It creates a new object with a specific object as its prototype. The prototype is not copied however. This is important, because changes you make to the prototype affect the object (like you did above with `eats`). Maybe that's what you meant, I just wanted to clarify it :) – Felix Kling May 23 '15 at 11:17
  • `writable: false` maybe? Why would we want others to change it? ;-) – thefourtheye May 23 '15 at 15:31
  • @thefourtheye: Yeah, that would probably be useful. I'm just setting it to the default values :) – Felix Kling May 23 '15 at 15:35
0

this is OOP approach in JS

var Person = function(name) {
    this.name = name;
}

Person.prototype.greeting = function(){ 
    var newPerson = new Person(this.name);
    alert("Greetings, I am " + name);
    return newPerson;
} 

Person.prototype.toString=function(){ 
    return '[Person "'+this.name+'"]';
} 

Woman.prototype = new Person();

Woman.prototype.constructor=Woman;      

function Woman(name){ 
    this.name=name;
} 

Woman.prototype.toString=function(){ 
    return '[Woman "'+this.name+'"]';
} 

var somePerson = new Person('Mr.');
var myPerson = new Woman('She');
alert('somePerson is '+somePerson);   // results in 'somePerson is [Person "Mr."]' 
alert('myPerson is '+myPerson);             // results in 'myPerson is [Woman "She"]' 

myPerson.greeting();                    // calls a method inherited from Person 
Özgür Ersil
  • 6,909
  • 3
  • 19
  • 29