A number of readers made some good comments on my article on JScript typing that deserve to be called out in more detail.
First, I was being a little sloppy in my terminology — I casually conflated static typing with strong typing, and dynamic typing with weak typing. Thanks for calling me on that. Under the definitions proposed by the reader, JScript would be a dynamically typed language (because every variable can take a value of any type) and a strongly typed language (because every object knows what type it is.) By contrast, C++ is a statically typed language (because every variable must have a type, which the compiler enforces) but also a weakly typed language (because the reinterpret cast allows one to turn pointers into integers, and so on.)
Second, a reader notes that one of the shortcomings of JScript is that though it is a strongly typed language (in our new sense) that it is a royal pain to actually determine the runtime type an object. The typeof operator has a number of problems:
- null is listed as being of the object type, though technically it is a member of the Null type.
- primitives (strings, numbers, Booleans) wrapped in objects are listed as being of the object type rather than their underlying type.
- JScript, unlike VBScript, does not interrogate COM objects to determine the class name.
- If JScript is passed a variant from the outside world that it cannot make sense of then typeof returns “unknown”.
Perhaps there is some other way. Prototype inheritance affords a kind of type checking, for example.
Prototype inheritance works like this: every JScript object has an object (or possibly null) called its prototype object. Suppose an object foo
has prototype object bar
, and bar
has prototype object baz
, and baz
has prototype object null
. If you call a method on foo
then JScript will search foo
, bar
and baz
for that method, and call the first one it finds.
The idea is that one object is a prototypical object, and then other objects specialize it. This allows for code re-use without losing the ability to dynamically customize behaviour of individual objects.
Prototypes are usually done something like this:
var Animal = new Object(); // omitted: set up Animal object function Giraffe() { // omitted: initialize giraffe object. } Giraffe.prototype = Animal; var Jerry = new Giraffe();
Now Jerry
has all the properties and methods of an individual Giraffe
object AND all the properties and methods of Animal
. You can use IsPrototypeOf
to see if a given object has Animal
on its prototype chain. Since prototype chains are immutable once created, this gives you a pretty reliable sort of type checking.
Note that Giraffe
is not a prototype of Jerry
. Note also that Animal
is not the prototype of Giraffe
! The object which is assigned to the prototype property of the constructor is the prototype of the instance.
Now, you all are not the first people to point out to me that determining types is tricky. A few years ago someone asked me what the differences are amongst
if (func.prototype.IsPrototypeOf(instance))
and
if (instance.constructor == func)
and
if (instance instanceof func)
The obvious difference is that the first one looks at the whole prototype chain, whereas the second two look at the constructor, right? Or is that true? Is there a semantic difference between the last two?
There is. Let’s look at some examples, starting with one that seems to show that there is no difference:
function Car() { } var honda = new Car(); print(honda instanceof Car); // true print(honda.constructor == Car); // true
It appears that instance instanceof func
and instance.constructor == func
have the same semantics. They do not. Here’s a more complicated example that demonstrates the difference:
var Animal = new Object(); function Reptile() { } Reptile.prototype = Animal; var lizard = new Reptile(); print(lizard instanceof Reptile); // true print(lizard.constructor == Reptile); // false
In fact lizard.constructor
is equal to Object
, not Reptile
.
Let me repeat what I said above, because no one understands this the first time — I didn’t, and I’ve found plenty of Javascript books that get it wrong. When we say
Reptile.prototype = Animal;
this does not mean “the prototype of Reptile
is Animal
“. It cannot mean that because (obviously!) the prototype of Reptile
, a function object, is Function.prototype
. No, this means “the prototype of any instance of Reptile
is Animal
“. There is no way to directly manipulate or read the prototype chain of an existing object in JScript.
Now that we’ve got that out of the way, the simple one first:
instance instanceof func
means “is the prototype
property of func
equal to any object on instance’s prototype chain?” So in our second example, the prototype property of Reptile
is Animal
and Animal
is on lizard
‘s prototype chain.
But what about our first example where there was no explicit assignment to the Car
prototype?
The compiler creates a function object called Car
. It also creates a default prototype object and assigns it to Car.prototype
. So again, when we way
print(honda instanceof Car);
the instanceof
operator gets Car.prototype
and compares it to the prototype chain of honda
. Since honda
was constructed by Car
it gets Car.prototype
on its prototype chain.
To sum up the story so far, instance instanceof func
is actually a syntactic sugar for func.prototype.IsPrototypeOf(instance)
This explains why lizard instanceof Reptile
returns true
— Reptile.prototype
is a prototype of lizard
.
So what the heck is going on with the constructor
property then? How is it possible that we can say lizard = new Reptile();
and at the same time lizard.constructor == Reptile
is false
???
Let’s go back to our simple first example. I said above that since Car
has no prototype assigned to it, we create a default prototype. During the creation of the default prototype, the interpreter assigns Car
to Car.prototype.constructor
. That might be a little confusing, so let’s look at some pseudocode. This:
function Car(){}
logically does the same thing as
var Car = new Function(); Car.prototype = new Object(); Car.prototype.constructor = Car;
Now we say
var honda = new Car(); print(honda.constructor == Car );
and what happens? honda
has no constructor
property, so it looks on the prototype chain for any object with a constructor
property. In this case Car.prototype
is on the prototype chain and it has a constructor
property equal to Car
, so the comparison is true
. Remember, any property of an object’s prototype object is treated as a property of the object itself – that’s what “prototype” means.
Now let’s look at our second example:
var Animal = new Object(); function Reptile(){ } Reptile.prototype = Animal;
Logically this does the same thing as
var Animal = new Object(); var Reptile = new Function(); Reptile.prototype = new Object(); Reptile.prototype.constructor = Reptile; Reptile.prototype = Animal;
Whoops. The default prototype has been thrown away. Now when we say
print(lizard.constructor == Reptile );
what happens? lizard
does not have a constructor
property, so we look at the prototype chain and find Animal
. But Animal
also does not have a constructor
property either! So we look on Animal
‘s prototype chain. Animal
was constructed via new Object()
so therefore it has Object.prototype
on its prototype chain, and Object.prototype
has a constructor
property. As you might expect from our previous discussion of how the constructor
property is initialized, Object.prototype.constructor
is set to Object
.
Therefore lizard.constructor
is equal to Object
, not Reptile
, even though lizard
is an instance of Reptile
and was constructed by the Reptile
function object!
You would think that the script engine would automatically assign the constructor
property to the object when it was constructed, but it does not. It assigns the property to the prototype and relies on prototype inheritance. I was not a member of the ECMAScript committee when this decision was made, so I don’t know why we standardized this rather bizarre behaviour, but we’re stuck with it now!
Notes from 2020:
This article is one I referred back to so many times over the years I worked on JScript; the prototype mechanism can be very confusing.
There were a few interesting comments when I first wrote this. A couple highlights:
- In some implementations of ECMAScript you can manipulate the proto chain directly with the __proto__ property. Make sure you understand what you’re doing before you change it!
- Using “new String” to make a string can lead to an object that has different properties than just making the “primitive” string. This is by design — bad design, but design nevertheless. This was the subject of part three of this series.
Pingback: The JScript Type System, Part Three: If It Walks Like A Duck… | Fabulous adventures in coding