0

Suppose that I have a javascript constructor:

function Person(name) {
    this.name = name;
    this.hello = function () { return "It's a-me, " + name + "!"; };
}

the Person "type" has a convenient method, hello that I would like to re-use on another type Student. I would like for a Student to have the following structure:

function Student(name) {
    this.name = name;
    this.hello = function () { return "It's a-me, " + name + "!"; };
    this.books = [];
}

One option is to use the code for Student as-is above. This is sub-optimal for the usual reasons, such as that if I want it to mirror the Person type, then I have to manually keep their code in sync. Anyway, this is not good.

A second option (from this answer) is to do something like:

function Student(name) {
    Person.call(this, name);
    this.books = [];
}

When I mario = new Student("mario") I get the following:

Object { name: "mario", hello: hello(), books: [] }

I've successfully achieved the inheritance that I wanted, but this has the unfortunate property of placing all of the desired properties into my object. Notably, for example, there is a "hello" property on mario. It would be nice if that "hello" property could be looked up in the prototype chain.

How can I neatly create a prototype chain given the relevant object constructors?

Him
  • 5,257
  • 3
  • 26
  • 83
  • 1
    "*It would be nice if that "hello" property could be looked up in the prototype chain.*" - then why did you start having `Person` create the property for each instance? Instead, start by placing the method on `Person`s `.prototype`! – Bergi Jan 27 '23 at 19:32
  • 1
    Also, why not use `class` syntax which simplifies all of this? – Bergi Jan 27 '23 at 19:33
  • @Bergi I'm familiar with the `class` syntax. AFAIK, it's just syntactic sugar on top of the prototype deep magic, which I am trying to comprehend here. Still, if you were to post an answer to this with the `class` syntax, and explain how it solves this problem, I would upvote it. – Him Jan 27 '23 at 19:39

4 Answers4

1

When you create an object with new, the this value of your constructor function is set to the object, and that object's prototype is set to the prototype of the constructor Function being called.

That's why your properties are currently being added to the created object.

function Student {
    this.name = name
}

const student = new Student('John')

// is (almost) equivalent to the following

const student = {}
student.name = 'John'

But if you want to add properties to the prototype instead, so that you can use inheritance, then in ES5 Javascript you can do so by assigning properties directly to the prototype of your constructor function.

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

// Person is a function
// Its prototype is an instance of Object, and it has no properties
// i.e. something like Person.prototype = new Object()

Person.prototype.hello = function() {
    return 'It is I, ' + this.name
}

// Now Person.prototype has one property, "hello", which is a function.

function Student(name) {
    Person.call(this, name)
    this.books = [];
}

// Student is a function
// Its prototype is also an instance of Object with no properties

// the following is the magic line
Student.prototype = Object.create(Person.prototype)

// We replace the prototype of the Student function with a new object, but
// Object.create() allows us to set the prototype to an existing object, in this case Person.prototype,
// Person.prototype is itself an instance of Object, and we previously
// added the "hello" function to it as a property.

const student = new Student('John')

// So what happens here?
// First a new object is created, and its prototype is set to Student.prototype
// Then we call Person.call(this)
// Which executes the body of the Person function
// So you get the properties on the object itself through the body of the Person and Student functions
// And you get the shared functionality through the prototype chain of instance -> Student.prototype -> Person.prototype -> Object.prototype

Hope that helps!

Julien Zakaib
  • 196
  • 10
  • This seems to exactly replicate the prototype chain that the equivalent `class` syntax creates. – Him Jan 27 '23 at 19:49
  • Julien, @Pointy suggests (and the equivalence with `class` syntax suggests) that there are good reasons for the prototype chain to have methods on the prototype and to have properties trickle down to the object in just this fashion. Could you or @Pointy comment briefly on why this is the correct thing to do? – Him Jan 27 '23 at 19:53
  • 1
    @Him https://stackoverflow.com/questions/310870/use-of-prototype-vs-this-in-javascript#comment17110527_310870 – Bergi Jan 27 '23 at 19:57
0

You can use prototyping method or class sugar method as you want. Here is a simple example :

function Student(name) {
    this.name = name;
    this.books = [];
}
Student.prototype.hello = function(){
    return "It's a-me, " + this.name + "!";
}
Student.prototype.addBook = function(book){
    this.books.push(book);
}
Student.prototype.getBooks = function(){
    return this.books;
}

let mario = new Student("Mario");

console.log(mario.hello());
mario.addBook("prototyping");
mario.addBook("chain");
console.log(mario.getBooks());

class Person {
  constructor(name) {
    this.name = name;
    this.books = [];
  }
    hello(){
        return "It's a-me, " + this.name + "!";
    }
    addBook(book){
        this.books.push(book);
    }
    getBooks(){
        return this.books;
    }
}

let luigi = new Person("Luigi");
console.log(luigi.hello());
luigi.addBook("classSugar");
luigi.addBook("classType");
console.log(luigi.getBooks());
tatactic
  • 1,379
  • 1
  • 9
  • 18
-1

For longer chains use Object.assign, here is an example of making a GradStudent that is both a Student and a Person and has the personality of a Comedian and also has the properties and methods of a 4th class GameCharacter:

(function() {
  //Person
  function Person(name) {
    this.name = name;
    this.helloString = "Hello my name is "
  }
  Person.prototype.name = "Bob";
  Person.prototype.hello = function() {
     return this.helloString + this.name;
  };

  //Student
  function Student(name, books) {
    Person.call(this, name);
    this.books = books;
  }
  Student.prototype = Object.create(Person.prototype);
  Student.prototype.constructor = Student;
  Student.prototype.books = ["math","reading"];
  
  //Comedian
  function Comedian(name) {
     Person.call(this,name);
  };
  Comedian.prototype = Object.create(Person.prototype);
  Comedian.prototype.constructor = Comedian;
  Comedian.prototype.hello = function() {
      return "I don't know what my parents where thinking when they name me Squat, just kidding, my name is " + this.name;
  };
  
  //GameCharacter
  function GameCharacter(power) {
     this.power = power;
  };
  GameCharacter.prototype = new Object();
  GameCharacter.prototype.constructor = GameCharacter;
  GameCharacter.prototype.gainPower = function(power) {
     this.power += ", "+power;
  };
  GameCharacter.prototype.statePower = function() {
    return this.power;
  };
  
  //GradStudent
  function GradStudent(name, books, degree) {
    Comedian.call(this, name);
    Student.call(this,name,books);
    GameCharacter.call(this, "jumping");
    this.degree = degree;
    this.gainPower("flying");
  }
  GradStudent.prototype = Object.create(Student.prototype);
  Object.assign(GradStudent.prototype, Comedian.prototype, GameCharacter.prototype);
  GradStudent.prototype.constructor = GradStudent;
  
  
  var gradStudent = new GradStudent("Bill",["C", "C++", "JavaScript"], "B.S.");
  
  console.log(gradStudent.hello() + " I have a " + gradStudent.degree +" I am studying " + gradStudent.books.toString() + ". \n In a game I play my power's are "+ gradStudent.statePower() + ". \n Is gradStudent also a Student? " + (gradStudent instanceof Student) + "" );
  
  var otherStudent = new Student("Jennifer" ,["english", "science"]);
  
  console.log(gradStudent.books.toString()  +  " " + otherStudent.books.toString());

})();

GradStudent is an instance of Student, it's a type of Student, and can also do all the things Comedian and GameCharacter does. The value of Object.assign is that kind of multiple inheritance.

mrall
  • 140
  • 4
  • This doesn't make sense. Isn't `GradStudent` supposed to inherit from `Student`? – Bergi Jan 28 '23 at 02:59
  • @Bergi It does inherit from Student and Student inherits from Person. Object.assign gives GradStudent everything from the prototype of Student. GradStudent gets the books property from Student Notice that books is only defined in Student yet a new Gradstudent has a books property as well. Object.create makes it inherit name and hello from Person and Object.assign makes it inherit books from Student. – mrall Jan 30 '23 at 19:09
  • No, it does not *inherit* from `Student`. You're mixing in `Student.prototype` for some reason, but you inherit from `Person`, and `gradStudent instanceof Student` is false. Why not simply inherit normally from `Student` which in turn inherits from `Person` already?! – Bergi Jan 30 '23 at 19:13
  • Btw, don't use `Student.prototype.books = [];`. That set of books will shared by *all* student instances, something you definitely don't want. – Bergi Jan 30 '23 at 19:15
  • I edited the snippet to show that your wrong. – mrall Jan 30 '23 at 19:26
  • Well you edited the snippet to fix it, I wasn't wrong :-) Thanks! But the `Object.assign` is superfluous now. – Bergi Jan 30 '23 at 19:28
  • You are wrong, I did not change the code, just added more to what is output to the console, gradStudent instanceof Student is true not false like you were saying. It's easy to pollyfill Object.assign if needed. – mrall Jan 30 '23 at 19:30
  • Uh, [the revision history](https://stackoverflow.com/posts/75264843/revisions) clearly shows how you changed `Object.create(Person.prototype);` into `Object.create(Student.prototype);` to make it work? – Bergi Jan 30 '23 at 19:34
  • Btw see [my edit](https://stackoverflow.com/posts/75264843/revisions) for what I meant. Feel free to roll it back if you don't like it. – Bergi Jan 30 '23 at 19:36
  • Oh, yes, I was testing that and did not put it back, trying to put it back now. nevertheless the books property does get separated. – mrall Jan 30 '23 at 19:37
  • Ok, I see what your saying now, but what if we want GradStudent to inherit from 3 or more Types? – mrall Jan 30 '23 at 19:44
  • You can make the prototype chain as long as you want. But you can't inherit from multiple independent classes, that is only possible with mixins in JS. – Bergi Jan 30 '23 at 19:53
  • Right, and Object.assign is for that. I've edited the snippet to show making GradStudent a construct of 3 Classes. – mrall Jan 30 '23 at 20:10
  • Ah, yes. Unfortunately it is now what the OP was asking about – Bergi Jan 30 '23 at 20:13
  • Yes it is, he wants to know ways to pass around/inherit a prototype chain, and two ways are Object.create and Object.assign. – mrall Jan 30 '23 at 20:16
-2

I can accomplish such a thing with the following:

function Student(name) {
    Object.setPrototypeOf(this, new Person(name));
    this.books = [];
}

However, I'm not familiar enough with javascript to know what possible problems might arise with this solution. Coming from other OO style languages, it feels weird for the prototype of mario to be an actual instance of a Person, but I suppose everything in js is an instance, in some sense, so this might just be bias on my part.

Him
  • 5,257
  • 3
  • 26
  • 83
  • 2
    That's not the right way to set the prototype, for various complicated reasons. The right way is to do `Student.prototype = Object.create(Person.prototype);` *outside* of the constructor. You only have to do it once, and then every `Student` instance will get the right prototype. Better, use the `class` declaration syntax that JavaScript has had for several years now. – Pointy Jan 27 '23 at 19:11
  • I could not get your suggestion to work. Possibly you are missing some details? I would appreciate if you would elaborate in an answer. @Pointy [Here is a jsfiddle](https://jsfiddle.net/vtkmLyh1/2/) implementing your suggestion. – Him Jan 27 '23 at 19:22
  • I did notice that this solution puts the `.name` attribute on the prototype, whereas the `class` syntax (along with `super` etc) only puts the `hello` method in the prototype (actually, in the prototype of `Person`s as well!), and puts the `.name` attribute on the `mario` object. – Him Jan 27 '23 at 19:29
  • 1
    Oh well you'll also need to create the "hello" function properly on `Person.prototype` instead of in the constructor. Creating functions like that in the constructor when they could just as well be on the prototype is also something that the new `class` declaration syntax will clean up for you. – Pointy Jan 27 '23 at 19:31
  • Note that the answer you reference in your question is from **2014**. JavaScript has been changing very rapidly since that time. – Pointy Jan 27 '23 at 19:32
  • @Pointy I also attempted this by putting `hello` on the `Person.prototype` to no avail. `mario` doesn't get a `name` either. [Here is a fiddle](https://jsfiddle.net/vtkmLyh1/3/) with `hello` on the `Person` prototype. – Him Jan 27 '23 at 19:35
  • Your code calls `.hello()` but it doesn't do anything with the result. If you `console.log(mario.hello())` you'll see the result. Also `mario` should be declared with `let` or `const`. – Pointy Jan 27 '23 at 19:39
  • @Pointy no. In the provided fiddle, `mario.hello()` prints `It's a-me, result`. `mario.name` is `undefined`. I think the "result" business is jsfiddle nonsense. If you copy-paste the code into a browser console, `mario.name` is still `undefined`. – Him Jan 27 '23 at 19:41
  • [Fixed fiddle.](https://jsfiddle.net/wd6y8n3q/1/) The Student constructor needs to invoke the parent class constructor. Also the `.hello()` method needs to use `this.name` and not simply `name`. – Pointy Jan 27 '23 at 19:45
  • @Pointy not sure if you were planning to post your fiddle as an answer, but you seem to have been scooped by Julien. Just fyi. – Him Jan 27 '23 at 19:50