Stephen
Stephen

Reputation: 532

instanceof operator returns false on subsequent change to inheritance chain

When a prototype is set on a constructor function, the instanceof operator only returns true until the prototype is changed. Why?

function SomeConstructorFunction() {
}

function extendAndInstantiate(constructorFn) {
    constructorFn.prototype = {}; //Can be any prototype
    return new constructorFn();
}

var child1 = extendAndInstantiate(SomeConstructorFunction);
console.log(child1 instanceof SomeConstructorFunction); //true

var child2 = extendAndInstantiate(SomeConstructorFunction);
console.log(child1 instanceof SomeConstructorFunction); //false
console.log(child2 instanceof SomeConstructorFunction); //true

Upvotes: 7

Views: 4383

Answers (4)

Aadit M Shah
Aadit M Shah

Reputation: 74204

Prototypal inheritance can be a bit of a brain bender. I've already written a simple explanation for prototypal inheritance in the following answer and I suggest you read it: https://stackoverflow.com/a/8096017/783743

Now to answer your question. Consider the following function:

function F() {}

I can create an instance of F using new as follows:

var f = new F;

As expected f instanceof F returns true. This is because the instanceof operator checks for F.prototype in the prototype chain of f and returns true if it is there. See the answer I linked to above.

Now say I create a new function G as follows and set F.prototype to G.prototype:

function G() {}

F.prototype = G.prototype;

If I now evaluate f instanceof F again false is returned. This is because F.prototype is no longer in the prototype chain of f (remember that F.prototype is now G.prototype).

Now let's create a new instance of F:

var g = new F;

If you evaluate g instanceof G it'll return true even though g = new F. This is because G.prototype exists in the prototype chain of g.

This is not a bug. It's the way JavaScript works. In fact we can exploit this feature to create some really interesting functions: Instantiate JavaScript functions with custom prototypes

Upvotes: 4

Diode
Diode

Reputation: 25145

Each time you call extendAndInstantiate a new object is created using {} and it is assigned as the prototype of SomeConstructorFunction. This clears the link between existing instances and SomeConstructorFunction. So only final one will be a valid instance of SomeConstructorFunction.

function SomeConstructorFunction() {

}

function extendAndInstantiate(constructorFn) {
  constructorFn.prototype = {}; //New instance as prototype
    return new constructorFn();
}

var child1 = extendAndInstantiate(SomeConstructorFunction);
console.log(child1 instanceof SomeConstructorFunction); //true

var child2 = extendAndInstantiate(SomeConstructorFunction);
console.log(child1 instanceof SomeConstructorFunction); //false
console.log(child2 instanceof SomeConstructorFunction); //true


var child3 = extendAndInstantiate(SomeConstructorFunction);
console.log(child1 instanceof SomeConstructorFunction); //false
console.log(child2 instanceof SomeConstructorFunction); //false
console.log(child3 instanceof SomeConstructorFunction); //true

So either you can follow the method showed by @ben336 or do a condition check before assigning prototype as shown below.

function Base(){

}

function SomeConstructorFunction() {

}

function extendAndInstantiate(constructorFn, Base) {
  if(!(constructorFn.prototype instanceof Base)){
    console.log(" extend only once ");
    constructorFn.prototype = new Base(); 
  }
  return new constructorFn();
}


var child1 = extendAndInstantiate(SomeConstructorFunction, Base);
console.log(child1 instanceof SomeConstructorFunction); //true

var child2 = extendAndInstantiate(SomeConstructorFunction, Base);
console.log(child1 instanceof SomeConstructorFunction); //true
console.log(child2 instanceof SomeConstructorFunction); //true


var child3 = extendAndInstantiate(SomeConstructorFunction, Base);
console.log(child1 instanceof SomeConstructorFunction); //true
console.log(child2 instanceof SomeConstructorFunction); //true
console.log(child3 instanceof SomeConstructorFunction); //true

Upvotes: 0

MattDiamant
MattDiamant

Reputation: 8771

constructorFn.prototype = {}; //Can be any prototype

Not true!

constructorFn.prototype = Object.prototype;

Or any other native prototype will make them all true.

The reason is that the first time that you're calling extendAndInstantiate(), you're setting the SomeConstructorFunction's prototype to something (here an empty object {}). For child1, instanceOf will only return true while SomeConstructorFunction's prototype is that exact instance of {}. When you run extendAnInstantiate() a second time, you're changing SomeConstructorFunction's prototype to a different instance of {}, so child2 will be an instanceOf that new {}, but child1 is still an instanceOf of the old {}, so child1 will return false, while child2 returning true. If you set the prototype to a common prototype, such as Object.prototype or Array.prototype, it will always return true.

Another way to illustrate this is to pull {} out of the function, and assign it to a variable obj.

var obj = {};
function extendAndInstantiate(constructorFn) {
    constructorFn.prototype = obj;
    return new constructorFn();
}

Now this will always return true, because you aren't creating a new {} every time you run the function.

Upvotes: 3

Ben McCormick
Ben McCormick

Reputation: 25728

instanceof is based on the prototype object.

function extendAndInstantiate(constructorFn) {
constructorFn.prototype = {}; //Can be any prototype
...

When you set the prototype to {} you're creating a new object and setting it as the prototype. Because it is then not the same object as the prototype, instanceof returns false.

A setup like this will return true for all cases, because the prototype will be the same object:

function SomeConstructorFunction() {
}

var emptyObj = {};

function extendAndInstantiate(constructorFn) {
    constructorFn.prototype = emptyObj; 
    return new constructorFn();
}

var child1 = extendAndInstantiate(SomeConstructorFunction);
console.log(child1 instanceof SomeConstructorFunction); //true

var child2 = extendAndInstantiate(SomeConstructorFunction);
console.log(child1 instanceof SomeConstructorFunction); //true
console.log(child2 instanceof SomeConstructorFunction); //true

Upvotes: 0

Related Questions