JavaScript Prototype & Inheritance

javascript
Back

JavaScript is often described as a prototype-based language — to provide inheritance, objects can have a prototype object, which acts as a template object that it inherits methods and properties from. - MDN

Plain Object

const student = {
name: "Mohan",
};
console.log(Object.getPrototypeOf(student)); // Object.prototype
console.log(student.__proto__); // Object.prototype
// so
Object.getPrototypeOf(student) === Object.prototype; // true

Point to note here are:

  1. Object.prototype is nothing but a plain object with some props/methods like hasOwnProperty, isPrototypeOf etc
  2. Object.prototype is the prototype of newly created object by default(not of a class object).
  3. All props/methods of Object.prototype are available on student object.

In other words, we could say that student object is inherited from Object.prototype object.

Note: __proto__ is deprecated property, instead use Object.getPrototypeOf method.

Functions

function Student(name) {
this.name = name;
}
console.log(Student.prototype);
// Output: {}

functions are special type of object, they are callable and have a prototype property which is nothing but empty object.

So we could add properties/methods on function's prototype object.

Student.prototype = {
getName: function () {
return this.name;
},
};
const mohan = new Student("Mohan");
const john = new Student("John");
console.log(mohan);
// Output:
`
{
name: 'mohan',
__proto__: { getName: ƒ getName() }
}
`;
console.log(john);
// Output:
`
{
name: 'john',
__proto__: { getName: ƒ getName() }
}
`;
console.log(Object.getPrototypeOf(mohan));
// Output:
`
{ getName: ƒ getName() }
`;
console.log(mohan.valueOf());
`
{
name: 'Mohan',
__proto__: { getName: ƒ getName() }
}
`;

Point to note:

  1. prototype of all instances of Student is Student.prototype
  2. props/methods of Student.prototype shared among all objects of that class.

Prototype Chain

Did you noticed that mohan.valueOf isn't available on Student.prototype, right? but still we can access it. How?

As you know, valueOf is the method of Object.prototype.

When we do mohan.valueOf(), JS engine look up for method valueOf on mohan object first, then on its prototype which is Student.prototype then on Object.prototype

like:

mohan -------> Student.prototype -------> Object.prototype

so Object.prototype is dead end for lookup chain. If prop/method doesn't found there, will throw an error.

This is lookup [[Get]] operation.

Object.create

Object.create() method can be used to create a new object instance.

let ishaan = Object.create(mohan);
console.log(ishaan);
// Output:
`
{ __proto__: { name: 'Mohan' } }
`;
console.log(Object.getPrototypeOf(ishaan));
`
{
name: 'Mohan',
__proto__: { getName: ƒ getName() }
}
`;

so with Object.create we can change prototype of object to argument we passed.

The constructor property(Not a constructor function)

lets look at below code-

function Student(first, last, age, gender, interests) {
// property and method definitions
this.name = {
first: first,
last: last,
};
this.age = age;
this.gender = gender;
//...see link in summary above for full definition
}
let bob = new Student("Bob", "Smith", 32, "male", ["music", "skiing"]);
console.log(bob.constructor === Student.prototype.constructor); // true
console.log(Student.prototype.constructor === Student); // true
console.log(bob.constructor === Student); // false
console.log(bob.constructor === Student.constructor); // false

So Every constructor function(Student) has a prototype property whose value is an object containing a constructor property(Student.prototype.constructor). This constructor property points to the original constructor function.

Points to note are:

So with this knowledge we could also do:

let karen = new bob.constructor("Karen", "Stephenson", 26, "female", [
"playing drums",
"mountain climbing",
]);
console.log(karen);
// Output:
`
Student {
name: { first: 'Karen', last: 'Stephenson' },
age: 26,
gender: 'female',
__proto__: { constructor: ƒ Student() }
}
`;
// This works well as bob.constructor is Student

Inheritance - Prototype based

function Person(first, last, age, gender, interests) {
this.name = {
first,
last,
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
function Student(first, last, age, gender, interests, grades) {
Person.call(this, first, last, age, gender, interests);
this.grades = grades;
}
Object.defineProperty(Student.prototype, "constructor", {
value: Student,
enumerable: false,
writable: true,
});
const mohan = new Student("Mohan", "Dere", 30, "male", ["swim", "read"], 90);
console.log(mohan);
// Output:
`
Student {
name: { first: 'Mohan', last: 'Dere' },
age: 30,
gender: 'male',
interests: [ 'swim', 'read' ],
grades: 90,
__proto__: { constructor: ƒ Student() }
}
`;

Now lets add methods on Person.prototype so that we can use it in sub class.

Person.prototype = {
  getName: function() {
    return `${this.name.first} ${this.name.last}`;
  }
}

now when you try to access it on instance of sub class(Student) it won't work.

const mohan = new Student("Mohan", "Dere", 30, "male", ["swim", "read"], 90);
console.log(mohan.getName()); // TypeError: mohan.getName is not a function

to fix this we have to modify our original implementation to set up Student()'s prototype and constructor reference to Person()

function Person(first, last, age, gender, interests) {
this.name = {
first,
last,
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
Person.prototype = {
getName: function () {
return `${this.name.first} ${this.name.last}`;
},
};
function Student(first, last, age, gender, interests, grades) {
Person.call(this, first, last, age, gender, interests);
this.grades = grades;
}
Student.prototype = Object.create(Person.prototype);
Object.defineProperty(Student.prototype, "constructor", {
value: Student,
enumerable: false, // so that it does not appear in 'for in' loop
writable: true,
});
const mohan = new Student("Mohan", "Dere", 30, "male", ["swim", "read"], 90);
console.log(mohan.getName()); // Mohan Dere

That's all.

Inheritance - Class based(ECMAScript 2015 Classes)

class Person {
constructor(first, last, age, gender, interests) {
this.name = {
first,
last,
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
greeting() {
console.log(`Hi! I'm ${this.name.first}`);
}
}
class Student extends Person {
constructor(first, last, age, gender, interests, subject, grade) {
super(first, last, age, gender, interests);
// subject and grade are specific to Teacher
this.subject = subject;
this.grade = grade;
}
}

Hope so this post helps you to understand how prototype and inheritance works in JS.

Thanks!

MIT © Mohan Dere.
-