I have a couple of questions about the code.
1) In the student constructor, does Person.call(this, firstName) give student access to the methods of person? or does it change the context of this?
2) I assume Student.prototype = Object.create(Person.prototype) gives us acess to the methods and properties of person?
I'm having a hard time understanding the call method.
var Person = function(firstName) {
this.firstName = firstName;
};
Person.prototype.walk = function(){
console.log("I am walking!");
};
function Student(firstName, subject) {
Person.call(this, firstName);
this.subject = subject;
};
Student.prototype = Object.create(Person.prototype); // See note belo
Student.prototype.constructor = Student;
Student.prototype.sayHello = function(){
console.log("Hello, I'm " + this.firstName + ". I'm studying "
+ this.subject + ".");
};
1) No, it sets the this value and passes an argument, but as it sets the this value of Person to whatever this is inside Student, it could also give Person access to Student, but not the other way around.
All call(this-value, [arg1,arg2]) and apply(this-value, [[arg1,arg2]]) does, is call the function with a given this-value, and arguments.
2) Yes, Student.prototype = Object.create(Person.prototype) copies the prototype of Person and sets it as the prototype of Student, giving Student the same prototyped methods as Person, but a copy, not the same reference.
Related
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?
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!
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());
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.
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.
My proto reference of "mike" instance points to "Student" but as its name shows "Person" , I don't know why is that. Here is my code and screenshot of console below:
const Person = function (firstName,birthYear) {
this.firstName = firstName;
this.birthYear = birthYear;
}
Person.prototype.calcAge = function () {
console.log(2037 - this.birthYear);
}
const Student = function (firstName, birthYear, course){
Person.call(this,firstName,birthYear);
this.course = course;
};
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student;
Student.prototype.introduce = function () {
console.log(`My name is ${this.firstName} and ,I study ${this.course}`);
}
const mike = new Student('Mike',2020, 'Computer Science');
console.log(mike);
When I check on console it shows Person:
Student.prototype = Object.create(Person.prototype)
In this case, you are creating a new object and make it the value of Student.prototype. The new object Student has Person.prototype as its prototype.
The very first thing you see in Chrome's console is Student. That is the "type" of your mike object.
On the other hand, the __proto__ property of mike references the prototype from which this instance was created. Your code has explicitly defined that prototype with the following statement:
Student.prototype = Object.create(Person.prototype);
So, from that moment on, Student.prototype instanceof Person is true, but Student.prototype instanceof Student false. And all instances created with new Student, will reference the Student.prototype in their __proto__ property. So what you see is as expected.
The fact that the following assignment was made, doesn't change this behaviour:
Student.prototype.constructor = Student;
This is a cosmetic change, and doesn't determine the nature of the instances that Student creates. That might have mislead you.
class syntax
The code you provided performs the steps needed to set up a prototype chain, where a Student inherits from a Person.
In modern JavaScript it is much less cryptical to make this happen. Nowadays you would write this example as follows:
class Person {
constructor(firstName,birthYear) {
this.firstName = firstName;
this.birthYear = birthYear;
}
calcAge() {
console.log(2037 - this.birthYear);
}
}
class Student extends Person {
constructor(firstName, birthYear, course) {
super(birthYear, course);
this.course = course;
}
introduce() {
console.log(`My name is ${this.firstName} and ,I study ${this.course}`);
}
}
const mike = new Student('Mike',2020, 'Computer Science');
console.log(mike);
The result in the console will also here confirm that the prototype object of a Student instance is an instance of Person (not of Student).
let's suppose we have an object Person as follows:
function Person(name){
this.name = name;
this.greeting = function(){
alert("Hi I'm " + this.name);
}
}
and its child
function Teacher(name, subject){
Person.call(this, name);
this.subject = subject;
Teacher.prototype = Object.create(Person.prototype);
}
I tried to override greeting method as follows:
Teacher.prototype.greeting = function(){
alert("Hello my name is " + this.name + " and I teach " + this.subject);
}
but teacher1.greeting() invokes Person's method and not Teacher's one, as you can see here:
Where's the mistake?
UPDATED ANSWER:
Now that I'm home and on a laptop, I see the bug. You set the Teacher prototype in the wrong place.
So you needed to do this:
// Incorrect
function Teacher(first, last, age, gender, interest, subject) {
Person.call(this, first, last, age, gender, interest);
this.subject = subject;
Teacher.prototype = Object.create(Person.prototype);
}
// Correct
function Teacher(first, last, age, gender, interest, subject) {
Person.call(this, first, last, age, gender, interest);
this.subject = subject;
}
Teacher.prototype = Object.create(Person.prototype);
Because of this, every time you instantiated a new instance of Teacher, you would override it’s prototype with Person. So no matter what you set the Teacher's prototype to, it was getting overwritten.
In your first class greeting function on Person function prototype.
If you use this in constructor function, then you refer to newly created object, not to function's prototype. This is correct way if you want to override prototype function:
function Person(name){
this.name = name;
}
Person.prototype.greeting = function(){
alert("Hi I'm " + this.name);
}
You can compare this code with yours by checking value of Person.prototype.greeting.
That's why you couldn't override it with Teacher class, cause greeting function was overwritten by Person constructor every time you create Teacher object and call it's constructor.
The problem is that you define greeting as an instance method in Person. It should be a method of Person.prototype. Why? Because when you reference a property of an instance the interpreter always first checks for existence of instance properties, then the constructor prototype properties of an instance.
So this should work:
const someTeacher = new Teacher("Peter", "Jones", 26, "Male", "Functional Programming", "Math");
someTeacher.greeting();
function Person(first, last, age, gender, interest) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interest = interest;
// set the Person.prototype greeting method
// if not already done
if (!Person.prototype.greeting) {
Person.prototype.greeting = function() {
console.log("Hi I'm " + this.name.first);
};
}
}
function Teacher(first, last, age, gender, interest, subject) {
Person.call(this, first, last, age, gender, interest);
this.subject = subject;
// set Teachers prototype to Person
// if not already done
if (!Teacher.prototype.greeting) {
// override [greeting] first. If you do this after
// pointing the prototype to Person, it wil not
// work, probably because in that case you're
// overwriting Person.prototype.greeting which is
// a nogo
Teacher.prototype.greeting = function() {
console.log("Hello my name is " + this.name.first + " and I teach " + this.subject);
};
// set prototype to Person
Teacher.prototype = Person;
}
}
given MDN JS Inheritance article, we have these lines
My question is, why use Object.create and not just Person.prototype?
I understand the need to link prototypes.
But here is console example rendering the call to Object.create in fact not connecting the inherited methods:
Why is that? is it mistake in the article?
Teacher.prototype = Person.prototype
That sets the prototye of the teachers to the same object as the persons prototype. So if you change that:
Teacher.prototype.hi = () => alert("hi");
Then that exists both on teachers and persons:
new Person().hi();
Thats not what you want when creating a subclass. If you do
Teacher.prototype = Object.create( Person.prototype );
You create a new object that inherits the persons prototype. Now the properties do not exist on the object itself, but they are inherited. That getOwnPropertyNames returns nothing does not mean that the properties are not inherited but the opposite: They just don't exist on the object itself, but on its parent.
new Teacher().greeting(); // works :)
The problem with Teacher.prototype = Person.prototype is that then, there is no actual inheritence going on - both prototypes will reference the same object. If you proceed to add a function to Teacher's prototype, for example getClassTaught(), that will mutate Person.prototype, which should not have that method.
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() { return this.name; };
function Teacher(name, className) {
this.name = name;
this.className = className;
}
Teacher.prototype = Person.prototype;
Teacher.prototype.getClassTaught = function() { return this.className; };
const person = new Person();
console.log('getClassTaught' in person);
You also wouldn't be able to shadow Person functions without replacing them entirely. For example, if there's a greeting() function on Person.prototype, and you assign another greeting() function to Teacher.prototype, you'll be overwriting the function on Person.prototype - other persons calling greeting() may not work anymore, because the function is now Teacher-specific, rather than Person-generic.
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() { return this.name; };
Person.prototype.greeting = function() { return 'Hi, I am ' + this.name; };
function Teacher(name, className) {
this.name = name;
this.className = className;
}
Teacher.prototype = Person.prototype;
Person.prototype.greeting = function() { return 'Hi, I am Teacher ' + this.name; };
const person = new Person('Bob');
console.log(person.greeting());
getOwnPropertyNames only shows you the property names directly on the object itself - it does not show inherited property names. When you use Object.create(Person.prototype), greeting is inherited from the Person prototype; it's not directly on Teacher.prototype, so it doesn't show up in getOwnPropertyNames.
Consider the following example where Student inherits from Person:
function Person(name) {
this.name = name;
}
Person.prototype.say = function() {
console.log("I'm " + this.name);
};
function Student(name, id) {
Person.call(this, name);
this.id = id;
}
Student.prototype = new Person();
// Student.prototype.constructor = Student; // Is this line really needed?
Student.prototype.say = function() {
console.log(this.name + "'s id is " + this.id);
};
console.log(Student.prototype.constructor); // => Person(name)
var s = new Student("Misha", 32);
s.say(); // => Misha's id is 32
As you can see, instantiating a Student object and calling its methods works just fine, but Student.prototype.constructor returns Person(name), which seems wrong to me.
If I add:
Student.prototype.constructor = Student;
then Student.prototype.constructor returns Student(name, id), as expected.
Should I always add Student.prototype.constructor = Student?
Could you give an example when it is required ?
Read this SO question Prototype inheritance. obj->C->B->A, but obj.constructor is A. Why?.
It should give your an answer.