instanceof 操作符的运行原理是什么?
instanceof 操作符是 JavaScript 中用于检测数据类型的常用方法,特别是用于判断一个对象是否属于某个构造函数(类)的实例。
它的核心运行原理可以用一句话概括:检查右侧构造函数的 prototype 属性,是否出现在左侧对象的原型链(Prototype Chain)上。
下面为您详细拆解它的运行机制、底层逻辑以及如何手写实现。
1. 基本语法
javascript
object instanceof constructor
// L instanceof R (L 代表左侧对象,R 代表右侧构造函数)
2. 核心运行步骤(底层逻辑)
当 JavaScript 引擎执行 L instanceof R 时,它会执行以下步骤:
- 获取右侧的
prototype: 拿到构造函数R的显式原型对象,即R.prototype。 - 获取左侧的原型: 拿到对象
L的隐式原型,即L.__proto__(标准写法为Object.getPrototypeOf(L))。 - 比对与遍历原型链:
- 判断
L.__proto__是否等于R.prototype。 - 如果相等,则返回
true。 - 如果不相等,则沿着
L的原型链继续往上找,即将L.__proto__.__proto__作为新的对比目标,再次与R.prototype对比。 - 一直循环这个过程,直到原型链的尽头(
null)。如果到了null还没找到相等的原型,则返回false。
- 判断
3. 手写实现 instanceof
为了更直观地理解,我们可以用代码模拟 instanceof 的底层查找过程:
javascript
function myInstanceof(left, right) {
// 1. 基础数据类型直接返回 false (instanceof 只能判断对象)
if (typeof left !== 'object' || left === null) {
return false;
}
// 2. 获取右侧构造函数的 prototype 属性
let rightPrototype = right.prototype;
// 3. 获取左侧对象的 __proto__ (隐式原型)
let leftProto = Object.getPrototypeOf(left);
// 4. 开启循环,沿着原型链向上查找
while (true) {
// 如果找到原型链尽头 (Object.prototype.__proto__ 是 null),说明没找到
if (leftProto === null) {
return false;
}
// 如果左右原型相等,说明找到了,返回 true
if (leftProto === rightPrototype) {
return true;
}
// 如果没找到,继续沿着原型链向上获取下一层原型
leftProto = Object.getPrototypeOf(leftProto);
}
}
// 测试:
let arr = [];
console.log(myInstanceof(arr, Array)); // true
console.log(myInstanceof(arr, Object)); // true
4. 原型链查找图解示例
以 [] instanceof Object 为例:
left是[],right是Object。- 第一次对比:
[].__proto__指向Array.prototype。它与Object.prototype不相等。 - 沿着原型链往上走:查找
Array.prototype.__proto__。 - 第二次对比:
Array.prototype.__proto__指向Object.prototype。它与Object.prototype相等! - 查找结束,返回
true。
5. instanceof 的局限性与特殊情况
- 无法准确判断基本数据类型:
instanceof只能用于对象。对于字面量创建的基本类型,它会返回false。javascript"hello" instanceof String; // false new String("hello") instanceof String; // true - 原型链被修改会导致判断不准:
如果手动修改了对象的__proto__或者构造函数的prototype,instanceof的结果会随之改变。javascriptfunction Foo() {} let f = new Foo(); Foo.prototype = {}; // 改变了构造函数的 prototype console.log(f instanceof Foo); // false,因为 f.__proto__ 还是指向原来的那个原型对象 - 多全局环境(iframe)问题:
在浏览器中,如果你把一个 iframe 里的数组传给父窗口,在父窗口中使用arr instanceof Array会返回false。因为不同的 iframe 拥有各自独立的全局对象和构造函数(父窗口的Array和 iframe 里的Array内存地址不同)。
6. ES6 的扩展:Symbol.hasInstance
在 ES6 中,JavaScript 允许通过自定义 Symbol.hasInstance 方法来更改 instanceof 的默认行为:
javascript
class MyClass {
// 静态方法,重写 instanceof 的行为
static [Symbol.hasInstance](obj) {
return obj.isMyClass === true;
}
}
let testObj = { isMyClass: true };
console.log(testObj instanceof MyClass); // true,尽管 testObj 的原型链上并没有 MyClass.prototype
实际上,L instanceof R 运算在现代 JS 引擎中的第一步,是检查 R 是否有 [Symbol.hasInstance] 方法,如果有,就调用它并返回它的强制布尔结果;如果没有,才会降级去走上面提到的原型链比对逻辑。