Just JavaScript - The Search for Type |
Written by Ian Elliot | ||||
Monday, 22 October 2018 | ||||
Page 3 of 3
The Mystery of Instanceof - SubtypeKnowing what type an object is isn't the end of the story. In a class-based language the type hierarchy means that it is possible to consider an object as being of a number of different types and subtypes. For example, every object is an instance of Object as well as whatever class it actually is. If class B inherits from class A then an instance of class B can be considered to be a class A instance as well as it having all of the properties and methods of an instance of class A. This is the Liskov substitution principle and it applies, with some exceptions due to dynamic properties, in JavaScript. See the previous chapter for a full discussion. For example, if an object d has a prototype chain: c → b → a → Object.prototype → null then it is not only an instance of D but it can also be treated as an instance of C, B or A. With this explained we can now understand the intention of the instanceof operator. The instanceof operator looks as if it is a way of testing to see if an object was constructed by a particular constructor, but this isn't what it does at all. It is often explained that we can test to see if obj has constructor C using: obj instanceof C; which will return true if obj was constructed by C. This looks as if it is just an alternative to testing an object's constructor property, but again this is not what it is doing. The instanceof operator doesn't check the constructor but the prototype that the constructor specifies. That is: obj instanceof C: compares the object's prototype to the prototype specified in the constructor. If they are the same then it might seem reasonable to conclude that obj was constructed by C. But things are a little more complicated than this simple introduction suggests. Not only does instanceof test to see if the prototype the constructor specifies is the immediate prototype of the object, it also checks to see if the prototype is anywhere in the object’s complete prototype chain. So to be 100% clear: obj instanceof C; looks for C.prototype in the object's entire prototype chain and returns true if it finds it. Instanceof doesn't test that the object was created by the constructor you specify. It tests if the object has the prototype specified by the constructor anywhere in the object's prototype chain. So if obj instanceof C is true you can't conclude that C was the object’s constructor. All you can conclude is that object has a specific prototype object P in its prototype chain and hence it has all of the properties of P. You could say that the statement: obj instanceof C being true lets you conclude that obj is of type P, where P is the prototype object specified by C. For example consider the following:
We have defined two different constructors A and B with the same prototype p. That is the inheritance branches: p Notice that A and B construct objects with different sets of properties - an instance of A has x and z, and an instance of B has x and y. Now if you create an object using B: var b=new B(); and test using instanceof: console.log(b instanceof A); you will discover that b is an instance of A (i.e. the result is true) even though it was created by B. You can also see that there is no reasonable sense in which b is an instance of A - it only shares the same prototype with objects created by A. In this sense it would be more reasonable to say that b was a p. So why did anyone add instanceof to JavaScript? If you restrict yourself to a strictly non-branching inheritance structure then instanceof does sort of work. If you change the previous example to one in which B "inherits" from A then it makes a little more sense:
You can see that now we have an inheritance with no branches given by: b → a → p → object.prototype → null i.e. any object created by B has all of the properties of one created by A and both have all of the properties of p. Now if you create an instance of B: var b=new B(); then: console.log(b instanceof A); is true because b has p in its prototype chain and because the inheritance is hierarchical it also has all of the properties of A. Notice also that in this case an A would not be considered to be an instance of a B because B's prototype is A and A is not in A's prototype chain. As long as you stick to a strict hierarchical non-branching"inheritance" using prototypes then the instanceof operator gives you the results you might expect. If the inheritance branches, as most do, then you get a seemingly wrong answer from instanceof if you regard it as telling you about constructors. Where Are We?JavaScript provides two "helper" functions for programmers wanting to reinvent type, but they both are very deeply flawed. Final version in book
Summary
This is an extract from the book Just JavaScript by Ian Elliot. Buy Now: from your local AmazonJust JavaScript
|
||||
Last Updated ( Monday, 22 October 2018 ) |