我花了一段时间才弄明白,但这真的值得花时间。首先,让我们看看instanceof 是如何工作的。
引用MDN,
instanceof 运算符测试对象的原型链中是否具有构造函数的prototype 属性。
[instanceof]
现在,让我们看看 ECMA 5.1 规范如何定义 instanceof,
产生式RelationalExpression: RelationalExpression instanceof ShiftExpression 评估如下:
- 让
lref 成为评估RelationalExpression 的结果。
- 让
lval 成为GetValue(lref)。
- 让
rref 成为评估ShiftExpression 的结果。
- 让
rval 成为GetValue(rref)。
- 如果
Type(rval) 不是Object,则抛出TypeError 异常。
- 如果
rval 没有[[HasInstance]] 内部方法,则抛出TypeError 异常。
- 返回调用
rval的[[HasInstance]]内部方法的结果,参数为lval。
首先计算左侧和右侧表达式 (GetValue),然后右侧结果应该是带有 [[HasInstance]] 内部方法的对象。并非所有对象都有[[HasInstance]] 内部方法,而是函数。比如下面的会失败
console.log(Object instanceof {});
# TypeError: Expecting a function in instanceof check, but got #<Object>
[[HasInstance]]
现在,让我们看看[[HasInstance]] 在 ECMA 5.1 规范中是如何定义的,
假设F 是一个函数对象。
当F的[[HasInstance]]内部方法以V的值被调用时,采取以下步骤:
- 如果
V 不是对象,则返回false。
- 让
O 成为调用F 的[[Get]] 内部方法的结果,属性名称为"prototype"。
- 如果
Type(O) 不是Object,则抛出TypeError 异常。
- 重复
- 让
V 成为V 的[[Prototype]] 内部属性的值。
- 如果
V 是null,则返回false。
- 如果
O和V引用同一个对象,返回true。
就是这么简单。将F的prototype属性与O的[[Prototype]]内部属性进行比较,直到变成F的null或prototype与O相同。
[[prototype]]内部属性
首先让我们看看[[prototype]] internal property是什么,
所有对象都有一个名为[[Prototype]] 的内部属性。该属性的值为null 或对象,用于实现继承。本机对象是否可以将主机对象作为其[[Prototype]] 取决于实现。每个[[Prototype]] 链必须具有有限长度(即,从任何对象开始,递归访问[[Prototype]] 内部属性最终必须导致null 值)。
注意:我们可以通过Object.getPrototypeOf函数获取这个内部属性。
prototype属性
[[HasInstance]] 还谈到了另一个名为 prototype 的属性,它特定于 Function 对象。
prototype 属性的值用于在 Function 对象作为新创建对象的构造函数调用之前初始化新创建对象的 [[Prototype]] 内部属性。
这意味着,当函数对象用作构造函数时,将创建一个新对象,并且新对象的内部[[Prototype]] 将使用此prototype 属性进行初始化。例如,
function Test() {}
Test.prototype.print = console.log;
console.log(Object.getPrototypeOf(new Test()) === Test.prototype);
# true
实际问题
现在让我们回到实际问题。让我们来看第一个案例
console.log(Object instanceof Function);
# true
它将首先获取Function.prototype,并尝试查找该对象是否在Object 的原型层次结构中。让我们看看结果如何
console.log(Function.prototype);
# [Function: Empty]
console.log(Object.getPrototypeOf(Object));
# [Function: Empty]
console.log(Object.getPrototypeOf(Object) === Function.prototype);
# true
由于Function.prototype 匹配Object 的内部属性[[Prototype]],它返回true。
现在让我们来看第二种情况
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
在这里,首先我们得到Object.prototype,即{}。现在它正在尝试查找{} 是否存在于Function 的原型链中。 Function 的直接父级 is 和 Empty 函数。
console.log(Object.getPrototypeOf(Function));
# [Function: Empty]
和Object.prototype不一样
console.log(Object.getPrototypeOf(Function) === Object.prototype);
# false
但[[HasInstance]] 算法并不止于此。它重复并再上一层
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Function)));
# {}
这与Object.prototype 相同。这就是返回 true 的原因。