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;
}
}
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.
I have a function constructor defined this way:
var Person = function (name, yearOfBirth, job) {
this.name = name;
this.yearOfBirth = yearOfBirth;
this.job = job;
}
Person.prototype.calculateAge = function () {
console.log(2016 - this.yearOfBirth);
};
Now I also have another function constructor called Teacher which I've defined this way:
var Teacher = function (name, yearOfBirth, subject) {
Person.call(this, name, yearOfBirth, "teacher");
this.subject = subject;
}
Now I create a new object called roySir this way:
var roySir = new Teacher("Roy", 1960, "English");
However when I try to do
roySir.calculateAge() I get an error saying that
"roySir.calculateAge is not a function"
How come the calculateAge function is not inherited here?
Another question I have is when I check:
roySir.hasOwnProperty("name") // true
Why is this true here? Isn't name a property of the parent class rather than an own property?
You should ensure that Teacher's prototype inherits from Person's prototype. Simply calling Person with a Teacher won't let Teacher inherit from Person's prototype methods:
var Person = function(name, yearOfBirth, job) {
this.name = name;
this.yearOfBirth = yearOfBirth;
this.job = job;
}
Person.prototype.calculateAge = function() {
console.log(2016 - this.yearOfBirth);
};
var Teacher = function(name, yearOfBirth, subject) {
Person.call(this, name, yearOfBirth, "teacher");
this.subject = subject;
}
Teacher.prototype = Object.create(Person.prototype);
var roySir = new Teacher("Roy", 1960, "English");
roySir.calculateAge();
You need the Object.create rather than Teacher.prototype = Person.prototype there so that mutations to Teacher.prototype won't undesirably change Persons that aren't Teachers - for example, if you gave Teacher.prototype a teachesClass method, you would want only Teachers to have access to that, but you wouldn't want a generic Person to have that method.
Alternatively, use ES6 and extends, which is more readable:
class Person {
constructor(name, yearOfBirth, job) {
this.name = name;
this.yearOfBirth = yearOfBirth;
this.job = job;
}
calculateAge() {
console.log(2016 - this.yearOfBirth);
}
}
class Teacher extends Person {
constructor(name, yearOfBirth, subject) {
super(...[name, yearOfBirth, subject, 'teacher']);
}
}
var roySir = new Teacher("Roy", 1960, "English");
roySir.calculateAge();
As for the name property, it's assigned to the object itself with this.name = name; - when a constructor is called, like with Person.call(this, ...), the this in the other constructor still refers directly to the object being created in the calling code - that's what call does, the first argument passed to it will be a direct reference to the this used in the other function.
The prototype chain looks like:
roySir { name, yearOfBirth, job }
above inherits from Teacher.prototype (empty)
above inherits from Person.prototype { calculateAge }
I'm trying to add a getter for a property defined on Person, so I can do test.fullName. The problem is, when I log test.fullName, it's undefined. Why does the getter work properly?
function Person(name, surname, yearOfBirth){
this.name = name,
this.surname = surname,
this.yearOfBirth = yearOfBirth };
Object.defineProperty(Person, 'fullName', {
get: function(){
return this.name +' '+ this.surname
}
});
var test = new Person("test", "test", 9999);
console.log(test.fullName);
You have to define the property on the Person's prototype property, so it's inherited on all instances.
Object.defineProperty(Person.prototype, 'fullName', {
get: function() {
return this.name +' '+ this.surname
}
});
Adding a property to just Person will make it static. You must do it on Person.prototype. You can read more at MDN. Per the link:
Prototypes are the mechanism by which JavaScript objects inherit features from one another
Thus, for all Person instances to inherit all properties such as fullName, define the property on Person.prototype.
Also, you are using commas instead semicolons. Use semicolons to terminate statements, not commas:
this.name = name;
this.surname = surname;
this.yearOfBirth = yearOfBirth;
You're defining the fullName property on Person. You should define it on Person.prototype, so it's inherited by instances:
function Person(name, surname, yearOfBirth) {
this.name = name;
this.surname = surname;
this.yearOfBirth = yearOfBirth;
};
Object.defineProperty(Person.prototype, 'fullName', {
get: function() {
return this.name + ' ' + this.surname
}
});
var test = new Person("test", "test", 9999);
console.log(test.fullName);
Side note: Don't use commas where you should have semicolons, as in the Person constructor. I've fixed that above as well.
Define it on the prototype.
Object.defineProperty(Person.prototype, 'fullName', {
get() {
return `${this.name} ${this.surname}`;
}
});
By "downstream" I mean outside of the custom-constructor later in the code.
i.e.
function Person (name, age){
this.name = name;
this.age = age;
}
var me = new Person("Margo", 22);
I can add methods downstream to my custom-constructor/class (see example below):
person.prototype.sayName = function(){
console.log("Hi my name is " + margo.sayName + " .");
}
How do I add a parameter to the custom-constructor downstream that refers to a string, or boolean, etc?
The custom-constructor would technically be changed to
function Person (name, age, newParameter){
this.name = name;
this.age = age;
this.newParameter = newParameter;
}
REMEMBER, I don't want to change the custom-constructor directly. I have posed this question to other programmers and they have answered it incorrectly by changing the original. The point is to change it outside("downstream") similar to adding a method by using prototype.
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.