In the section about inheritance in the MDN article Introduction to Object Oriented Javascript, I noticed they set the prototype.constructor:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
Does this serve any important purpose? Is it okay to omit it?
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 aprototype
object includes theconstructor
, 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:
When you call the constructor to instantiate, say
Adam
:The
new
keyword invoked with 'Person' basically will run the Person constructor with a few additional lines of code:If we
console.log(adam.species)
, the lookup will fail at theadam
instance, and look up the prototypal chain to its.prototype
, which isPerson.prototype
- andPerson.prototype
has a.species
property, so the lookup will succeed atPerson.prototype
. It will then log'human'
.Here, the
Person.prototype.constructor
will correctly point toPerson
.So now the interesting part, the so-called 'subclassing'. If we want to create a
Student
class, that is a subclass of thePerson
class with some additional changes, we'll need to make sure that theStudent.prototype.constructor
points to Student for accuracy.It doesn't do this by itself. When you subclass, the code looks like this:
Calling
new Student()
here would return an object with all of the properties we want. Here, if we checkeve instanceof Person
, it would returnfalse
. If we try to accesseve.species
, it would returnundefined
.In other words, we need to wire up the delegation so that
eve instanceof Person
returns true and so that instances ofStudent
delegate correctly toStudent.prototype
, and thenPerson.prototype
.BUT since we're calling it with the
new
keyword, remember what that invocation adds? It would callObject.create(Student.prototype)
, which is how we set up that delegational relationship betweenStudent
andStudent.prototype
. Note that right now,Student.prototype
is empty. So looking up.species
an instance ofStudent
would fail as it delegates to onlyStudent.prototype
, and the.species
property doesn't exist onStudent.prototype
.When we do assign
Student.prototype
toObject.create(Person.prototype)
,Student.prototype
itself then delegates toPerson.prototype
, and looking upeve.species
will returnhuman
as we expect. Presumably we would want it to inherit from Student.prototype AND Person.prototype. So we need to fix all of that.Now the delegation works, but we're overwriting
Student.prototype
with an ofPerson.prototype
. So if we callStudent.prototype.constructor
, it would point toPerson
instead ofStudent
. This is why we need to fix it.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 thenew
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 asuper
when we are extending an existing class. So we would write the above code like this:EDIT, I was actually wrong. Commenting the line out doesn't change it's behavior at all. (I tested it)
Yes, it is necessary. When you do
Student.prototype.constructor
becomesPerson
. Therefore, callingStudent()
would return an object created byPerson
. If you then doStudent.prototype.constructor
is reset back toStudent
. Now when you callStudent()
it executesStudent
, which calls the parent constructorParent()
, it returns the correctly inherited object. If you didn't resetStudent.prototype.constructor
before calling it you would get an object that would not have any of the properties set inStudent()
.This has the huge pitfall that if you wrote
but then if there was a Teacher whose prototype was also Person and you wrote
then the Student constructor is now Teacher!
Edit: You can avoid this by ensuring that you had set the Student and Teacher prototypes using new instances of the Person class created using Object.create, as in the Mozilla example.
Got a nice code example of why it is really necessary to set the prototype constructor..
Yes and no.
In ES5 and earlier, JavaScript itself didn't use
constructor
for anything. It defined that the default object on a function'sprototype
property would have it and that it would refer back to the function, and that was it. Nothing else in the specification referred to it at all.That changed in ES2015 (ES6), which started using it in relation to inheritance hierarchies. For instance,
Promise#then
uses theconstructor
property of the promise you call it on (via SpeciesConstructor) when building the new promise to return. It's also involved in subtyping arrays (via ArraySpeciesCreate).Outside of the language itself, sometimes people would use it when trying to build generic "clone" functions or just generally when they wanted to refer to what they believed would be the object's constructor function. My experience is that using it is rare, but sometimes people do use it.
It's there by default, you only need to put it back when you replace the object on a function's
prototype
property:If you don't do this:
...then
Student.prototype.constructor
inherits fromPerson.prototype
which (presumably) hasconstructor = Person
. So it's misleading. And of course, if you're subclassing something that uses it (likePromise
orArray
) and not usingclass
¹ (which handles this for you), you'll want to make sure you set it correctly. So basically: It's a good idea.It's okay if nothing in your code (or library code you use) uses it. I've always ensured it was correctly wired up.
Of course, with ES2015 (aka ES6)'s
class
keyword, most of the time we would have used it, we don't have to anymore, because it's handled for us when we do¹ "...if you're subclassing something that uses it (like
Promise
orArray
) and not usingclass
..." — It's possible to do that, but it's a real pain (and a bit silly). You have to useReflect.construct
.It is necessary when you need an alternative to
toString
without monkeypatching: