实现 instanceOf 操作符
基础知识
instanceof
操作符用于检测一个构造函数的 prototype
属性,是否存在于一个指定对象的原型链上。这是 JavaScript 中判断对象类型、实现继承等场景下的核心工具。要实现它,我们必须理解 JavaScript 的原型机制。
原型 (Prototype): 在 JavaScript 中,对象可以从其他对象继承属性。这个“其他对象”就是所谓的“原型”。每个对象都有一个指向其原型的内部链接。
Object.getPrototypeOf(obj)
: 这是 ES5 提供的标准方法,用于获取一个对象(obj
)的原型。在一些旧的实现或调试中,你可能也会看到非标准的__proto__
属性。构造函数的
prototype
属性: 每个函数在创建时,都会被自动赋予一个prototype
属性。这个属性的值是一个对象,我们称之为“原型对象”。当使用new
关键字调用该函数创建实例时,新实例的原型就会指向这个“原型对象”。原型链 (Prototype Chain): 实例的原型指向构造函数的原型对象,而原型对象本身也是一个对象,它也有自己的原型,这样一层层向上追溯,直到某个对象的原型为
null
(原型链的终点),就形成了一条“原型链”。
obj instanceof Constructor
的本质,就是在 obj
的整条原型链上,寻找是否存在 Constructor.prototype
这个对象。
核心思路
我们的目标是创建一个函数 myInstanceof(left, right)
,模拟原生 instanceof
的行为。核心思路可以概括为“顺藤摸瓜”:
- 首先,拿到“瓜”,也就是
right
(构造函数) 的原型对象right.prototype
。这是我们要寻找的目标。 - 然后,拿到“藤”的起点,也就是
left
(实例对象) 的原型Object.getPrototypeOf(left)
。 - 顺着“藤”一直向上爬(遍历原型链),在每一节都检查一下,看看手里的“藤”是不是我们要找的那个“瓜”。
- 如果爬到一半找到了,那就说明“瓜熟蒂落”,
left
是right
的实例,返回true
。 - 如果一直爬到藤的尽头(原型为
null
),都还没找到,那就说明它们没关系,返回false
。
代码实现
下面是 myInstanceof
的一个完整实现。
/**
* 自定义 instanceof 操作符
* @param {any} left - 要检查的实例对象
* @param {Function} right - 构造函数
* @returns {boolean} - 如果 left 是 right 的实例,则返回 true,否则返回 false。
*/
function myInstanceof(left, right) {
// 1. 校验右侧参数必须是一个函数
if (typeof right !== 'function') {
throw new TypeError('Right-hand side of "instanceof" is not a function');
}
// 2. 校验左侧参数必须是对象(或函数),原始值没有原型链
if (
left === null ||
(typeof left !== 'object' && typeof left !== 'function')
) {
return false;
}
// 3. 获取要寻找的目标:构造函数的原型对象
const targetPrototype = right.prototype;
// 4. 获取实例对象的原型,作为遍历的起点
let proto = Object.getPrototypeOf(left);
// 5. 开始循环,沿着原型链向上查找
while (proto) {
// 检查当前原型是否就是目标原型
if (proto === targetPrototype) {
return true;
}
// 如果不是,则将指针移到原型链的上一环
proto = Object.getPrototypeOf(proto);
}
// 6. 如果循环结束,说明已到原型链顶端,仍未找到
return false;
}
代码解析
- 参数校验:首先对
right
和left
进行校验。instanceof
的右边必须是函数,因为它需要访问.prototype
属性。- 左边必须是对象类型(函数也是对象),因为原始值(如
1
,'a'
,true
)没有原型链,直接判断为false
即可。
- 获取目标与起点:
targetPrototype
是我们要在原型链上寻找的终点。proto
是我们开始查找的起点,即实例left
的直接原型。
- 循环遍历(while (proto)):启动一个
while
循环,条件是proto
必须是一个真值(不能是null
)。因为原型链的终点是null
,所以当proto
变为null
时,循环会自动停止,代表已经搜完了整条链。 - 比较与上溯:
- if (proto === targetPrototype):在循环的每一步,都将当前的原型
proto
与我们的目标targetPrototype
进行严格比较。如果相等,证明目标已找到,函数立即返回true
。 - proto = Object.getPrototypeOf(proto);:如果不相等,就通过
Object.getPrototypeOf(proto)
获取当前原型的原型,相当于在链条上向后移动一步,准备进行下一次循环比较。
- if (proto === targetPrototype):在循环的每一步,都将当前的原型
- 返回结果:如果
while
循环因为proto
变为null
而自然结束,代码会执行到循环体之外。这说明整条原型链都被搜索过了,但没有找到目标,因此函数返回false
。