普通视图

发现新文章,点击刷新页面。
昨天 — 2026年4月15日首页

手写 instanceof:从原型链聊聊 JS 的实例判断

作者 暗不需求
2026年4月14日 19:10

大家好,我是平时爱折腾前端JavaScript的小伙。最近在看 JS 继承和原型相关的东西并且进行学习,发现我身边的人学习(包括我自己以前) instanceof 的理解还停留在“能判断对象是不是某个类的实例”这个表面。根据师兄的口述,仅仅了解这些是不够面试的。今天就借着这个机会,结合实际代码,一步步手写一个 instanceof,顺便把原型链、继承这些概念也捋清楚。

先说说为什么需要 instanceof

在大项目里,尤其是多人协作的时候,你经常会拿到一个对象,却不知道它到底是从哪个构造函数来的,有哪些方法和属性可用。这时候 instanceof 就特别实用——它就像其他面向对象语言里的“类型检查”运算符,能快速告诉你“这个对象是不是某个类的实例”。

简单说,A instanceof B 的本质就是:A 的原型链上有没有 B 的 prototype。如果有,就返回 true;没有,就 false

这不是 JS 独有的概念,很多 OOP 语言都有类似的机制,但 JS 是基于原型的,所以它的实现特别“接地气”——全靠那条 __proto__ 链。

原型和原型链是什么?

先用一个最常见的例子感受一下(来自 Array):

<script>
const arr = []; // 其实就是 new Array()
console.log(arr.__proto__, arr.__proto__.constructor, arr.constructor);
console.log(arr.__proto__.__proto__,
  arr.__proto__.__proto__.constructor,
  arr.__proto__.__proto__.__proto__,
  arr.__proto__.__proto__.__proto__.__proto__);
</script>

你会看到:

  • arr.__proto__ 指向 Array.prototype
  • Array.prototype.__proto__ 又指向 Object.prototype
  • 最后 Object.prototype.__proto__null,链条结束

这就是原型链:每个对象都有一个 __proto__ 属性(隐式原型),它指向自己构造函数的 prototype(显式原型)。沿着这条链一直往上找,就能找到所有能用的属性和方法(包括 toStringhasOwnProperty 这些)。

理解了这条链,instanceof 就很好解释了。

原生的 instanceof 是怎么工作的?

看下面这个经典的继承例子:

function Animal() {}
function Person() {}

Person.prototype = new Animal();
const p = new Person();

console.log(p instanceof Person);  // true
console.log(p instanceof Animal);  // true

pPerson 的实例,它的原型链上是 Person.prototype → Animal.prototype → Object.prototype → null,所以它既是 Person 的实例,也是 Animal 的实例。

手写一个 isInstanceOf

现在我们来自己实现一个。核心思路就一句话:从 left 的 __proto__ 开始,一路往上找,看能不能找到 right.prototype

完整代码如下(直接复制就能跑):

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>手写 instanceof</title>
</head>
<body>
<script>
// B 是否出现在 A 的原型链上
function isInstanceOf(left, right) {
  // 防止 right 不是函数
  if (typeof right !== 'function') {
    return false;
  }
  
  let proto = left.__proto__;
  while (proto) {
    if (proto === right.prototype) {
      return true;
    }
    proto = proto.__proto__; // 继续往上找,直到 null
  }
  return false;
}

function Animal() {}
function Cat() {}
Cat.prototype = new Animal();
function Dog() {}
Dog.prototype = new Animal();

const dog = new Dog();

console.log(isInstanceOf(dog, Dog));     // true
console.log(isInstanceOf(dog, Animal));  // true
console.log(isInstanceOf(dog, Object));  // true
console.log(isInstanceOf(dog, Cat));     // false
</script>  
</body>
</html>

这个函数和原生的 instanceof 行为几乎一致。注意两点小细节:

  1. 我们加了个 typeof right !== 'function' 的防护,防止传进来奇怪的东西报错。
  2. 循环结束条件是 proto 变成 null,这正是原型链的终点。

结合继承方式再看 instanceof

instanceof 最常出现在继承场景里。我们来对比几种常见的继承写法,看看它在每种方式下的表现。

1. 构造函数绑定继承(call/apply)

function Animal() {
  this.species = '动物';
} 
function Cat(name, color) {
  Animal.apply(this);  // 把 Animal 的属性绑到 this 上
  this.name = name;
  this.color = color;
}

const cat = new Cat('小黑', '黑色');
console.log(cat.species); // 动物

这种方式只继承了属性,没有把原型链连起来。所以 cat instanceof Animal 会是 false。如果你需要原型方法,就得配合后面两种方式用。

2. prototype 模式(推荐)

function Animal() {
  this.species = '动物';
}
function Cat(name, color) {
  this.name = name;
  this.color = color;
}

// 关键两步
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat; // 修复 constructor 指向

const cat = new Cat('小黑', '黑色');
console.log(cat instanceof Cat);    // true
console.log(cat instanceof Animal); // true

这里 Cat.prototype 直接指向一个 Animal 实例,原型链就连上了。记得一定要把 constructor 指回来,不然 cat.constructor 会指向 Animal,容易出 bug。

3. 直接继承 prototype(有坑)

function Animal() {}
Animal.prototype.species = '动物';

function Cat(name, color) {
  Animal.call(this);
  this.name = name;
  this.color = color;
}

Cat.prototype = Animal.prototype; // 直接引用
Cat.prototype.constructor = Cat;
Cat.prototype.sayHello = function() {
  console.log('hello');
};

const cat = new Cat('小黑', '黑色');
console.log(cat instanceof Cat);    // true
console.log(cat instanceof Animal); // true
console.log(Animal.prototype.constructor); // 变成了 Cat(副作用!)

这种写法性能好(不用 new 一个 Animal 实例),但会污染父类的 prototype。如果你在 Cat.prototype 上加方法,Animal 也能拿到,容易出意外。实际项目里还是推荐用第 2 种,或者用 Object.create(Animal.prototype) 做中介(空对象继承)。

结尾

手写 instanceof 其实就这么简单,核心就是遍历原型链。写完之后,你会对 JS 对象“到底是谁生的”这件事有更直观的理解。在大型项目里,它能帮你快速做类型守护、写工具函数,或者在框架里判断组件类型。

当然,原生 instanceof 已经够用了,我们手写主要是为了加深理解。下次再遇到“这个对象为啥有这个方法”“继承关系乱了”的时候,你就可以顺着 __proto__ 链自己排查了。

如果你也正在看原型链和继承,欢迎评论区一起讨论~代码我都放上去了,直接复制就能跑。希望这篇文章能让你少踩几个坑!并且希望你在面试的时候能拿下instanceof这一难点。早日拿下offer!

❌
❌