Why is it necessary to set the prototype constructor?
Solution 1
It's not always necessary, but it does have its uses. Suppose we wanted to make a copy method on the base Person
class. Like this:
// define the Person Class
function Person(name) {
this.name = name;
}
Person.prototype.copy = function() {
// return new Person(this.name); // just as bad
return new this.constructor(this.name);
};
// define the Student class
function Student(name) {
Person.call(this, name);
}
// inherit Person
Student.prototype = Object.create(Person.prototype);
Now what happens when we create a new Student
and copy it?
var student1 = new Student("trinth");
console.log(student1.copy() instanceof Student); // => false
The copy is not an instance of Student
. This is because (without explicit checks), we'd have no way to return a Student
copy from the "base" class. We can only return a Person
. However, if we had reset the constructor:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
...then everything works as expected:
var student1 = new Student("trinth");
console.log(student1.copy() instanceof Student); // => true
Solution 2
Does this serve any important purpose?
Yes and no.
In ES5 and earlier, JavaScript itself didn't use constructor
for anything. It defined that the default object on a function's prototype
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 the constructor
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.
Is it okay to omit it?
It's there by default, you only need to put it back when you replace the object on a function's prototype
property:
Student.prototype = Object.create(Person.prototype);
If you don't do this:
Student.prototype.constructor = Student;
...then Student.prototype.constructor
inherits from Person.prototype
which (presumably) has constructor = Person
. So it's misleading. And of course, if you're subclassing something that uses it (like Promise
or Array
) and not using class
¹ (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
class Student extends Person {
}
¹ "...if you're subclassing something that uses it (like Promise
or Array
) and not using class
..." — It's possible to do that, but it's a real pain (and a bit silly). You have to use Reflect.construct
.
Solution 3
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;
}
}
Solution 4
I'd disagree. It isn't necessary to set the prototype. Take that exact same code but remove the prototype.constructor line. Does anything change? No. Now, make the following changes:
Person = function () {
this.favoriteColor = 'black';
}
Student = function () {
Person.call(this);
this.favoriteColor = 'blue';
}
and at the end of the test code...
alert(student1.favoriteColor);
The color will be blue.
A change to the prototype.constructor, in my experience, doesn't do much unless you're doing very specific, very complicated things that probably aren't good practice anyway :)
Edit: After poking around the web for a bit and doing some experimentation, it looks like people set the constructor so that it 'looks' like the thing that is being constructed with 'new'. I guess I would argue that the problem with this is that javascript is a prototype language - there is no such thing as inheritence. But most programmers come from a background of programming that pushes inheritence as 'the way'. So we come up with all sorts of things to try and make this prototypical language a 'classic' language.. such as extending 'classes'. Really, in the example they gave, a new student is a person - it isn't 'extending' from another student.. the student is all about the person, and whatever the person is the student is as well. Extend the student, and whatever you've extended is a student at heart, but is customized to fit your needs.
Crockford is a bit crazy and overzealous, but do some serious reading on some of the stuff that he's written.. it'll make you look at this stuff very differently.
Solution 5
This has the huge pitfall that if you wrote
Student.prototype.constructor = Student;
but then if there was a Teacher whose prototype was also Person and you wrote
Teacher.prototype.constructor = Teacher;
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.
Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);
trinth
Updated on August 06, 2022Comments
-
trinth almost 2 years
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?
-
Wylie over 12 yearsGlad you asked this: I read the same documentation yesterday and was curious about the reasoning behind explicitly setting the constructor as well.
-
Marie almost 9 yearsI just had to point this out, this question is now linked in the article you linked!
-
nothingisnecessary over 8 yearsnothing is necessary
-
ckybonist about 5 yearsThe
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. -
Soner from The Ottoman Empire almost 4 years@KuanYuChu what kind of
unexpected result
? I really wonder.
-
-
Stephen over 12 yearsThe prototype constructure may become a person, but that is appropriate as it is inheriting all properties and methods from the Person. Creating a new Student() without setting the prototype.constructor appropriately calls its own constructor.
-
trinth over 12 yearsThanks, I did notice that omitting it did not affect my code, though it looks like it can come in handy sometimes.
-
Ansel Halliburton over 12 yearsNote: The
constructor
attribute does not have any special meaning in JS, so you might as well call itbananashake
. The only difference is that the engine automatically initializesconstructor
onf.prototype
whenever you declare a functionf
. However, it can be overwritten any time. -
Wayne over 12 years@Pumbaa80 - I get your point, but the fact that the engine automatically initializes
constructor
means that it does have special meaning in JS, pretty much by definition. -
Ansel Halliburton over 12 yearsYea, I was not very clear on that. I was trying to say that you can't assign a
constructor
to a "class" in order to change the behaviour ofnew
, but you have to do it the other way round: Assign aprototype
to the function. Having the circular reference by settingconstructor
may be a nice idea, but fails when two functions point to the sameprototype
. -
CEGRD over 11 yearsI just want to clarify that the reason why the behavior you said works is because you use
return new this.constructor(this.name);
instead ofreturn new Person(this.name);
. Sincethis.constructor
is theStudent
function (because you set it withStudent.prototype.constructor = Student;
), thecopy
function ends up calling theStudent
function. I am not sure what your intention was with the//just as bad
comment. -
CEGRD over 11 years@lwburk what do you mean by "//just as bad"?
-
Wayne over 11 years@CEGRD - I mean that using that method will also return a
Person
object where instead we should expect the more specificStudent
-
snapfractalpop over 10 yearsI think I get it. But, what if the
Student
constructor had added an additional argument like:Student(name, id)
? Do we then have to override thecopy
function, calling thePerson
version from within it, and then also copying the additionalid
property? -
Wayne over 10 yearsWell, something like that. I'd probably just create a whole new
copy
method that copies both properties. I don't see any need to call the "parent" method. -
binki about 9 yearsSetting
constructor
has just as many problems as directly callingnew Person()
. As @CEGRD said, the extending object’s function may be incompatible. Though, usingconstructor
like this as a shorthand can be justified if it happens to work in most cases as it reduces code duplication. But it's being used as a way for types to opt-in to a specific clone implementation, not as a thing in and of itself (other than that JavaScript happens to set it sometimes…). -
Bergi about 9 yearsYour
createNewCar
method is creating factories!? Also this looks like it should have been used asvar audiFactory = new CarFactory("Audi")
rather than using inheritance. -
Roumelis George about 9 yearsYou could just return Object.create(this) in the copy method
-
html_programmer almost 9 yearsWow, such simple way to achieve the concept of inheritance. I like the fact that it keeps the constructor property tied to the constructor function, which I found isn't always the case when applying other techniques. jsfiddle.net/2rmnce3g
-
Cypher almost 9 yearsThis does not inherit the prototype chain.
-
Stephen almost 9 years@Cypher slow clap welcome to the conversation, four years later. Yeah, the prototype chain is inherited, regardless of whether you overwrite the prototype.constructor. Try testing it out.
-
Cypher over 8 yearsYou're missing the code that inherits the prototype. Welcome to the internet.
-
Stephen over 8 years@Cypher Code snippet was based on the code in the linked article. Welcome to reading the question in its entirety. Oh. Wait.
-
schirrmacher over 8 years"there is no such thing as inheritence"??? Of course JavaScript uses prototypical inheritance! The only difference between JavaScript and "normal" inheritance languages is that it omits the abstraction of a class and you have to inherit directly from objects.
-
schirrmacher over 8 years@RoumelisGeorge Yes, but Object.create can't take arguments
-
Stephen over 8 years@macher I meant it as classical inheritance. Poor choice of wording on my part.
-
lucky over 8 years
Student.prototype = Object.create(...)
is assumed in this question. This answer adds nothing but possible confusion. -
Alex Ross over 8 years@AndréNeves I found this answer helpful.
Object.create(...)
is used in the MDN article that spawned the question, but not in the question itself. I'm sure many people don't click through. -
Aseem Bansal over 8 years
eve instanceof Student
returnedtrue
. See stackoverflow.com/questions/35537995/… for explanation. Also when you saywhich is, at the moment, nothing
what are you referring to? Every function has a prototype so if I checkStudent.prototype
it is something. -
bthehuman over 8 yearsMy mistake. It should have read 'eve instanceof Person' which would return false. I will amend that part. You are correct that every function has a prototype property. However, without assigning the prototype to
Object.create(Person.prototype)
, theStudent.prototype
is empty. So if we logeve.species
, it will not delegate properly up to its superclass, Person, and it will not log'human'
. Presumably, we want every subclass to inherit from its prototype and also its super's prototype. -
bthehuman over 8 yearsTo clarify, by
which is, at the moment, nothing
, I meant that theStudent.prototype
object is empty. -
bthehuman over 8 yearsMore on the prototype: Without the assignment of
Student.prototype
toObject.create(Person.prototype)
- which is, if you recall, the same way all instances of Person are set up to delegate toPerson.prototype
- looking up a property on an instance ofStudent
would delegate to onlyStudent.prototype
. Soeve.species
will fail its lookup. If we do assign it,Student.prototype
itself then delegates toPerson.prototype
, and looking upeve.species
will returnhuman
. -
Felix Kling over 8 yearsIt seems there are quite a few things wrong here: "It's necessary when you're attempting to emulate 'subclassing' [...] so that when you check if an instance is
instance
the 'subclass' Constructor, it will be accurate." Nope,instanceof
doesn't useconstructor
. "However, if we look up the student's .prototype.constructor, it would still point to Person" Nope, it will beStudent
. I don't understand the point of this example. Calling a function in a constructor is not inheritance. "In ES6, the constructor is now an actual function instead of a reference to a function" Uh what? -
bthehuman over 8 yearsHey Felix - you're correct.
instanceof
uses the prototype chain. Following the lineStudent.prototype = Object.create(Person.prototype)
, we've replaced the originalStudent.prototype
andStudent.prototype.constructor
would point toPerson
. I am currently fixing the response to remove this confusion. -
bthehuman over 8 years@FelixKling I also admit I'm confused by my own wording there. I mean to say in ES6, the
constructor
is actually a method given to us (albeit syntactic sugar) with keywords likesuper
for convenience - as opposed to being a 'plain' function that is invoked withnew
. -
Dmitri Zaitsev about 8 yearsYour example is using
this.constructor
internally, so it is not surprising it has to be set. Do you have any example without it? -
abhisekp almost 8 years@RoumelisGeorge
Object.create
would create an object with thestudent1
as its[[Prototype]]
and notStudent.prototype
as its[[Prototype]]
. -
RaelB almost 8 yearsOne point to add to this answer is mentioned in T. J. Crowder's answer. When you define the
Student
function, it'sprototype.constructor
isStudent
. However, whenStudent.prototype = Object.create(Person.prototype);
is called, a negative side effect is that theStudent.prototype.constructor
property is set toPerson
. Hence this can be fixed by setting the prototype.constructor back toStudent
. -
Drenai over 7 yearsThe linked article referenced in the question alreay uses Object.create(). This answer and the Edit ot the answer are not really relevant, and it's confusing to say the least:-)
-
James D over 7 yearsThe broader point is that there are gotchas that will snag people new to Javascript prototypes. If we're discussing in 2016, then you should really use ES6 classes, Babel, and/or Typescript. But if you really want to manually construct classes this way, it helps to understand how prototype chains really work to leverage their power. You can use any object as a prototype, and maybe you don't want to new a separate object. Furthermore, back before HTML 5 was fully widespread, Object.create was not always available, so it was easier to set up a class incorrectly.
-
Ry- about 6 yearsWhat is any of this supposed to do?
foo.constructor()
?? -
Rafał Spryszyński almost 6 yearsWhat a great answer! It is that simple. No need to work with prototypes directly if you know what you're doing.
-
Ini over 4 yearsI would not call this reset. Your new constructor property just gets found earlier in the prototype chain.
-
Wayne over 3 years@CEGRD I would also like to know what the heck I meant by that "just as bad" comment 🧐