It took a while for me to figure out but its really worth the time spent. First, let us see how instanceof
works.
Quoting from MDN,
The instanceof
operator tests whether an object has in its prototype chain the prototype
property of a constructor.
[instanceof]
Now, let us see how instanceof
is defined by ECMA 5.1 Specification,
The production RelationalExpression: RelationalExpression instanceof ShiftExpression
is evaluated as follows:
- Let
lref
be the result of evaluating RelationalExpression
.
- Let
lval
be GetValue(lref)
.
- Let
rref
be the result of evaluating ShiftExpression
.
- Let
rval
be GetValue(rref)
.
- If
Type(rval)
is not Object, throw a TypeError
exception.
- If
rval
does not have a [[HasInstance]]
internal method, throw a TypeError
exception.
- Return the result of calling the
[[HasInstance]]
internal method of rval
with argument lval
.
First the left and right hand side expressions are evaluated (GetValue
) and then right hand side result should be an Object with [[HasInstance]]
internal method. Not all objects will have [[HasInstance]]
internal method, but functions. For example, the following will fail
console.log(Object instanceof {});
# TypeError: Expecting a function in instanceof check, but got #<Object>
[[HasInstance]]
Now, let us see how [[HasInstance]]
has been defined in the ECMA 5.1 specification,
Assume F
is a Function object.
When the [[HasInstance]]
internal method of F
is called with value V
, the following steps are taken:
- If
V
is not an object, return false
.
- Let
O
be the result of calling the [[Get]]
internal method of F
with property name "prototype"
.
- If
Type(O)
is not Object, throw a TypeError
exception.
- Repeat
- Let
V
be the value of the [[Prototype]]
internal property of V
.
- If
V
is null
, return false
.
- If
O
and V
refer to the same object, return true
.
It is so simple. Take the prototype
property of F
and compare it with the [[Prototype]]
internal property of O
until it becomes null
or prototype
of F
is the same as O
.
[[prototype]]
internal property
First let us see what is the [[prototype]]
internal property,
All objects have an internal property called [[Prototype]]
. The value of this property is either null
or an object and is used for implementing inheritance. Whether or not a native object can have a host object as its [[Prototype]]
depends on the implementation. Every [[Prototype]]
chain must have finite length (that is, starting from any object, recursively accessing the [[Prototype]]
internal property must eventually lead to a null
value).
Note: We can get this internal property with the Object.getPrototypeOf
function.
prototype
property
[[HasInstance]]
also talks about another property called prototype
, which is specific to the Function
objects.
The value of the prototype
property is used to initialise the [[Prototype]]
internal property of a newly created object before the Function object is invoked as a constructor for that newly created object.
This means that, when a function object is used as a constructor, a new object will be created and the new object will have its internal [[Prototype]]
initialized with this prototype
property. For example,
function Test() {}
Test.prototype.print = console.log;
console.log(Object.getPrototypeOf(new Test()) === Test.prototype);
# true
Actual problem
Now let us get back to the actual question. Lets take the first case
console.log(Object instanceof Function);
# true
It will fetch Function.prototype
first and it will try and find if that object is in the prototype hierarchy of Object
. Let us see how that turns out
console.log(Function.prototype);
# [Function: Empty]
console.log(Object.getPrototypeOf(Object));
# [Function: Empty]
console.log(Object.getPrototypeOf(Object) === Function.prototype);
# true
Since the Function.prototype
matches the Object
's internal property [[Prototype]]
, it returns true
.
Now lets take the second case
console.log(Function instanceof Object);
# true
console.log(Object.prototype);
# {}
console.log(Object.getPrototypeOf(Function));
# [Function: Empty]
console.log(Object.getPrototypeOf(Function) === Object.prototype);
# false
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Function)));
# {}
Object.getPrototypeOf(Object.getPrototypeOf(Function)) === Object.prototype
# true
Here, first we get the Object.prototype
, which is {}
. Now it is trying to find if the same object {}
is there in the Function
's prototype chain. Immediate parent of Function
is and Empty function.
console.log(Object.getPrototypeOf(Function));
# [Function: Empty]
It is not the same as Object.prototype
console.log(Object.getPrototypeOf(Function) === Object.prototype);
# false
But the [[HasInstance]]
algorithm doesn't stop there. It repeats and gets up one more level
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Function)));
# {}
And this is the same as Object.prototype
. That is why this returns true
.