Appearance
Prototype Inheritance
In traditional class-based languages like Java and C++, inheritance essentially extends an existing class to create a new subclass. These languages strictly distinguish between classes and instances, making inheritance a type extension. However, JavaScript uses prototype inheritance, which does not have a direct concept of classes to extend.
Nevertheless, we can still achieve inheritance. Let's revisit the Student
constructor function:
javascript
function Student(props) {
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
Now, we want to extend Student
to create PrimaryStudent
. We can start by defining PrimaryStudent
:
javascript
function PrimaryStudent(props) {
// Call the Student constructor to bind this:
Student.call(this, props);
this.grade = props.grade || 1;
}
However, calling the Student
constructor does not mean PrimaryStudent
inherits from Student
. The prototype chain of objects created with PrimaryStudent
is:
new PrimaryStudent() --> PrimaryStudent.prototype --> Object.prototype --> null
We need to modify the prototype chain to:
new PrimaryStudent() --> PrimaryStudent.prototype --> Student.prototype --> Object.prototype --> null
This way, objects created based on PrimaryStudent
can call methods defined in both PrimaryStudent.prototype
and Student.prototype
.
Using a simple assignment like:
javascript
PrimaryStudent.prototype = Student.prototype;
won't work! This would make PrimaryStudent
and Student
share the same prototype object, negating the purpose of defining PrimaryStudent
.
We need an intermediary object to establish the correct prototype chain, with this intermediary object's prototype pointing to Student.prototype
. We can achieve this using an empty function F
:
javascript
// PrimaryStudent constructor:
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
}
// Empty function F:
function F() {}
// Set F's prototype to Student.prototype:
F.prototype = Student.prototype;
// Set PrimaryStudent's prototype to a new F instance:
PrimaryStudent.prototype = new F();
// Fix the constructor reference for PrimaryStudent:
PrimaryStudent.prototype.constructor = PrimaryStudent;
// Define methods on the PrimaryStudent prototype (the new F object):
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};
// Create an instance of PrimaryStudent:
let xiaoming = new PrimaryStudent({
name: 'Ming',
grade: 2
});
xiaoming.name; // 'Ming'
xiaoming.grade; // 2
// Verify the prototype chain:
xiaoming.__proto__ === PrimaryStudent.prototype; // true
xiaoming.__proto__.__proto__ === Student.prototype; // true
// Verify inheritance:
xiaoming instanceof PrimaryStudent; // true
xiaoming instanceof Student; // true
This establishes the new prototype chain as follows:
new PrimaryStudent() --> PrimaryStudent.prototype --> Student.prototype --> Object.prototype --> null
Note that function F
is only used as a bridge. We only created one instance of new F()
and did not alter the original prototype chain defined by Student
.
To encapsulate the inheritance process in a reusable inherits()
function and hide the definition of F
, we can simplify our code:
javascript
function inherits(Child, Parent) {
let F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
We can reuse this inherits()
function:
javascript
function Student(props) {
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
}
// Implement the prototype inheritance chain:
inherits(PrimaryStudent, Student);
// Bind additional methods to the PrimaryStudent prototype:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};
Summary
The implementation of prototype inheritance in JavaScript involves:
- Defining a new constructor function and using
call()
to invoke the constructor of the desired "parent" and bindthis
. - Using an intermediary function
F
to establish the prototype chain, preferably through the encapsulatedinherits()
function. - Continuing to define new methods on the prototype of the new constructor function.