Destroying constructor.prototype.constructor
Two days ago we replicated the new
operator in JavaScript. In doing so, I noticed a quirky little trend with inheritance, and decided to look into it. In this post, we'll do just that, and then we'll also replicate the instanceof
operator, since it can be equally confusing as new
. Alright, let's go.
To start, check out the following code:
function Car( color ){
this.color = color;
}
var car = new Car( "blue" );
car.__proto__ === car.constructor.prototype; // true
car.constructor; // function Car( color ) { this.color = color; }
function Truck( color ){
this.color = color;
}
// manually set the prototype property
Truck.prototype = { fourWheelDrive: true };
var truck = new Truck( "blue" );
truck.__proto__ === truck.constructor.prototype; // false
truck.constructor; // function Object() { [native code] }
Very interesting. Why is this? Well, it turns out that when you create a function, JavaScript sets a default prototype
property. This default property is an object with two properties itself: constructor
and __proto__
. When you create an object using an object literal {...}
, __proto__
gets defined, but constructor
does not. Therefore, when we override Truck.prototype
with an object literal, we lose the property Truck.prototype.constructor
. Now, when we instantiate some trucks, they have to go all the way up the prototype chain to Object.prototype.constructor
to find a constructor
property. Sure enough, we can check, and we see that Object.prototype.constructor === truck.constructor; // true
Looking at the two constructor functions' prototypes gave a good clue that something like this was happening
Car.prototype; // Car
Truck.prototype; // Object
As a quick aside, the original prototype.constructor
property that gets initialized on a function is actually a circular reference to the function itself. Check this out:
function hey(){}
hey.prototype.constructor.prototype.constructor.prototype.constructor === hey // true!
Great! So that makes some sense, but why does it matter? Well, if your relying on the constructor property of an object anywhere to accurately reflect the prototype chain, then manually setting a constructor function's prototype with an object literal is a bad idea. Here are a couple good solutions:
// 1. set the constructor property manually.
Truck.prototype = {
fourWheelDrive: true,
constructor: Truck
};
// 2. set properties on the prototype individually,
// so as not to overwrite the existing prototype object:
Truck.prototype.fourWheelDrive = true;
// 3. don't really care about the constructor property
// and try to make sure you aren't relying on it to behave consistently.
// Note:
// I think I'm going to go with option 2 in my code,
// but option 1 is also super useful for demo purposes,
// and option 3 is fine in > 95% of cases.
Okay, sounds good, but there's still one curious bit:
truck instanceof Truck; // true
The reason that this is curious is because we destroyed the link from constructor.prototype to Truck.prototype. However, we did not destroy the link via the __proto__ property.
Looking closely at some examples on the Mozilla Developer Network, we can learn that the instanceof
operator, when called like:
left_arg instanceof right_arg
looks up the prototype chain of the left_arg
for right_arg.prototype
.
Let's implement this real fast:
function instanceOf(object, constructor){
// we start one level up the prototype chain
var tempObject = object.__proto__;
while(tempObject !== null){
if(tempObject === constructor.prototype){
return true;
}
else{
tempObject = tempObject.__proto__;
}
}
return false;
}
It would also be not too hard to do this recursively:
function recursiveInstanceOf(object, constructor){
// ignore the case when object === constructor.prototype the first time
var tempObject = object.__proto__;
// base case
if( object === null ){
return false;
} else if ( object === constructor.prototype ) {
return true;
} else {
// look up the prototype chain
return recursiveInstanceOf( object.__proto__, constructor );
}
}