Reputation: 532
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
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
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
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
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