TLDR; Not super necessary, but will probably help in the long run, and it is more accurate to do so.
NOTE: Much edited as my previous answer was confusingly written and had some errors that I missed in my rush to answer. Thanks to those who pointed out some egregious errors.
Basically, it's to wire subclassing up correctly in Javascript. When we subclass, we have to do some funky things to make sure that the prototypal delegation works correctly, including overwriting a prototype
object. Overwriting a prototype
object includes the constructor
, so we then need to fix the reference.
Let's quickly go through how 'classes' in ES5 work.
Let's say you have a constructor function and its prototype:
//Constructor Function
var Person = function(name, age) {
this.name = name;
this.age = age;
}
//Prototype Object - shared between all instances of Person
Person.prototype = {
species: 'human',
}
When you call the constructor to instantiate, say Adam
:
// instantiate using the 'new' keyword
var adam = new Person('Adam', 19);
The new
keyword invoked with 'Person' basically will run the Person constructor with a few additional lines of code:
function Person (name, age) {
// This additional line is automatically added by the keyword 'new'
// it sets up the relationship between the instance and the prototype object
// So that the instance will delegate to the Prototype object
this = Object.create(Person.prototype);
this.name = name;
this.age = age;
return this;
}
/* So 'adam' will be an object that looks like this:
* {
* name: 'Adam',
* age: 19
* }
*/
If we console.log(adam.species)
, the lookup will fail at the adam
instance, and look up the prototypal chain to its .prototype
, which is Person.prototype
- and Person.prototype
has a .species
property, so the lookup will succeed at Person.prototype
. It will then log 'human'
.
Here, the Person.prototype.constructor
will correctly point to Person
.
So now the interesting part, the so-called 'subclassing'. If we want to create a Student
class, that is a subclass of the Person
class with some additional changes, we'll need to make sure that the Student.prototype.constructor
points to Student for accuracy.
It doesn't do this by itself. When you subclass, the code looks like this:
var Student = function(name, age, school) {
// Calls the 'super' class, as every student is an instance of a Person
Person.call(this, name, age);
// This is what makes the Student instances different
this.school = school
}
var eve = new Student('Eve', 20, 'UCSF');
console.log(Student.prototype); // this will be an empty object: {}
Calling new Student()
here would return an object with all of the properties we want. Here, if we check eve instanceof Person
, it would return false
. If we try to access eve.species
, it would return undefined
.
In other words, we need to wire up the delegation so that eve instanceof Person
returns true and so that instances of Student
delegate correctly to Student.prototype
, and then Person.prototype
.
BUT since we're calling it with the new
keyword, remember what that invocation adds? It would call Object.create(Student.prototype)
, which is how we set up that delegational relationship between Student
and Student.prototype
. Note that right now, Student.prototype
is empty. So looking up .species
an instance of Student
would fail as it delegates to only Student.prototype
, and the .species
property doesn't exist on Student.prototype
.
When we do assign Student.prototype
to Object.create(Person.prototype)
, Student.prototype
itself then delegates to Person.prototype
, and looking up eve.species
will return human
as we expect. Presumably we would want it to inherit from Student.prototype AND Person.prototype. So we need to fix all of that.
/* This sets up the prototypal delegation correctly
*so that if a lookup fails on Student.prototype, it would delegate to Person's .prototype
*This also allows us to add more things to Student.prototype
*that Person.prototype may not have
*So now a failed lookup on an instance of Student
*will first look at Student.prototype,
*and failing that, go to Person.prototype (and failing /that/, where do we think it'll go?)
*/
Student.prototype = Object.create(Person.prototype);
Now the delegation works, but we're overwriting Student.prototype
with an of Person.prototype
. So if we call Student.prototype.constructor
, it would point to Person
instead of Student
. This is why we need to fix it.
// Now we fix what the .constructor property is pointing to
Student.prototype.constructor = Student
// If we check instanceof here
console.log(eve instanceof Person) // true
In ES5, our constructor
property is a reference that refers to a function that we've written with the intent to be a 'constructor'. Aside from what the new
keyword gives us, the constructor is otherwise a 'plain' function.
In ES6, the constructor
is now built into the way we write classes - as in, it's provided as a method when we declare a class. This is simply syntactic sugar but it does accord us some conveniences like access to a super
when we are extending an existing class. So we would write the above code like this:
class Person {
// constructor function here
constructor(name, age) {
this.name = name;
this.age = age;
}
// static getter instead of a static property
static get species() {
return 'human';
}
}
class Student extends Person {
constructor(name, age, school) {
// calling the superclass constructor
super(name, age);
this.school = school;
}
}
subclass.prototype.constructor
will point toparent_class
if you don't writesubclass.prototype.constructor = subclass
; That is, usingsubclass.prototype.constructor()
directly will produce an unexpected result. – Phantasmunexpected result
? I really wonder. – Cichocki