Just JavaScript - The Search for Type |
Written by Ian Elliot | ||||
Monday, 22 October 2018 | ||||
Page 2 of 3
Finding the ConstructorSo how can you discover an object's constructor? At first it seems easy - every object has a constructor property which is set to reference its constructor function - but it isn't actually this easy and it often causes lots of confusion. There is a big problem with the constructor property in that it only references the correct constructor in a small number of situations. What is worse is that its behavior seems to be complicated and difficult to understand. The simplest way to follow what is happening is to discover how the constructor property is set. Recall that when you create a function object, any function object not just a constructor, the system automatically creates a prototype property and sets this to reference the new empty object. That is, what the system does is equivalent to:
Notice that the default prototype object is created when the constructor function is defined, not when it is called. As well as creating a default prototype object the system also creates a constructor property and sets it equal to the function. That is, what the system does is almost equivalent to:
The only subtle point that we can mostly ignore is that the constructor property is actually set on the prototype so it doesn't show as an own property. Don't worry about this for the moment, all that really matters is that you understand that the constructor property, like the prototype property, is created and initialized just once when the function is defined - and not each time it is called. This mechanism works just fine in simple situations. For example:
This function definition has resulted in a prototype property being created, referencing a new empty object, with a constructor property referencing the function object referenced by A. You can see that this is true by accessing A.prototype.constructor, e.g.: console.log(A.prototype.constructor); or more simply: console.log(A.constructor); which displays the definition of function A. In this case you can create an instance of A and check to see what its constructor is:
You will again see the definition of function A which is indeed the constructor or object a, i.e the final instruction is true. So in this simple case the prototype object that a constructor sets supplies the object constructed with a reference to its constructor - which is exactly what we need. So to test if the object referenced by a has been constructed by the constructor referenced by A you would use: if(a.constructor===A){ The condition is true if constructor and A reference the same Function object. Which is true in this case. The constructor property correctly identifies the constructor function in this case - but it is the only case where it does. Now consider setting your own custom prototype object:
The assignment of a prototype to B replaces the default prototype object created by the system. What happens is almost equivalent to:
You will notice that we have now lost the setting of the prototype's constructor property to B because we have replaced the default prototype object. The new prototype object created using A does have a constructor property but it is set to reference function A and not the correct constructor B. So now if you try:
you will find that the result is false. You will also find that: console.log(b.constructor===A); is true. The reason is that b doesn’t have a constructor property and the first prototype in the chain, i.e. the new instance of A, doesn’t have a constructor property but its prototype does and this is set by the system to A. Because the constructor property is only set for the default prototype object, it doesn't get updated correctly when you override this with some other prototype object, and overriding is what you have to do if you want to build up a prototype chain. As a result, in any prototype chain the constructor property can only be set correctly for the first object in the prototype chain, and then only if it is the default prototype object created by the constructor. This is the reason why it is often said that the constructor property gives the constructor, not of the object, but of the object's prototype. This is certainly true in this case but it is only because the prototype chain is just two objects long (ignoring Object.prototype and null). If you try:
You will discover that the constructor property is set to A and not B as it should be if it was the constructor of C's immediate prototype and not C if it was the constructor of c. It is set to A - the constructor of the first object in the prototype chain and not the constructor of C’s prototype i.e. B. The rule is that by default the constructor property gives the constructor of the first prototype in the chain as long as it hasn’t been explicitly set to an object. You can fix this default behavior by explicitly setting the constructor property each time you supply your own custom prototype object:
You will now find that each object does have a constructor property, supplied by its prototype, that does give the correct constructor. You can even work your way down the prototype chain finding each constructor in turn: console.log(C.prototype.constructor === C); console.log(C.prototype.prototype.constructor === B); console.log(C.prototype.prototype.prototype.constructor Notice that we are walking the prototype chain as defined by the constructors. We could do the same thing using the object via the getPrototypeOf method. So problem solved - as long as you remember to do the bookkeeping because JavaScript doesn't do it for you except in the case of a default prototype object. The only problem is that many beginners think that the constructor property “just works” - it doesn’t. To be precise:
|
||||
Last Updated ( Monday, 22 October 2018 ) |