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
instanceofoperator tests whether an object has in its prototype chain theprototypeproperty of a constructor.
[instanceof]
Now, let us see how instanceof is defined by ECMA 5.1 Specification,
The production
RelationalExpression: RelationalExpression instanceof ShiftExpressionis evaluated as follows:
- Let
lrefbe the result of evaluatingRelationalExpression.- Let
lvalbeGetValue(lref).- Let
rrefbe the result of evaluatingShiftExpression.- Let
rvalbeGetValue(rref).- If
Type(rval)is not Object, throw aTypeErrorexception.- If
rvaldoes not have a[[HasInstance]]internal method, throw aTypeErrorexception.- Return the result of calling the
[[HasInstance]]internal method ofrvalwith argumentlval.
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
Fis a Function object.When the
[[HasInstance]]internal method ofFis called with valueV, the following steps are taken:
- If
Vis not an object, returnfalse.- Let
Obe the result of calling the[[Get]]internal method ofFwith property name"prototype".- If
Type(O)is not Object, throw aTypeErrorexception.- Repeat
- Let
Vbe the value of the[[Prototype]]internal property ofV.- If
Visnull, returnfalse.- If
OandVrefer to the same object, returntrue.
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 eithernullor 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 anullvalue).
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
prototypeproperty 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.