大家好,我是平时爱折腾前端JavaScript的小伙。最近在看 JS 继承和原型相关的东西并且进行学习,发现我身边的人学习(包括我自己以前) instanceof 的理解还停留在"能判断对象是不是某个类的实例"这个表面。根据师兄的口述,仅仅了解这些是不够面试的。今天就借着这个机会,结合实际代码,一步步手写一个 instanceof,顺便把原型链、继承这些概念也捋清楚。
先说说为什么需要 instanceof
在大项目里,尤其是多人协作的时候,你经常会拿到一个对象,却不知道它到底是从哪个构造函数来的,有哪些方法和属性可用。这时候 instanceof 就特别实用------它就像其他面向对象语言里的"类型检查"运算符,能快速告诉你"这个对象是不是某个类的实例"。
简单说,A instanceof B 的本质就是:A 的原型链上有没有 B 的 prototype 。如果有,就返回 true;没有,就 false。
这不是 JS 独有的概念,很多 OOP 语言都有类似的机制,但 JS 是基于原型的,所以它的实现特别"接地气"------全靠那条 __proto__ 链。
原型和原型链是什么?
先用一个最常见的例子感受一下(来自 Array):
html
<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.prototypeArray.prototype.__proto__又指向Object.prototype- 最后
Object.prototype.__proto__是null,链条结束
这就是原型链 :每个对象都有一个 __proto__ 属性(隐式原型),它指向自己构造函数的 prototype(显式原型)。沿着这条链一直往上找,就能找到所有能用的属性和方法(包括 toString、hasOwnProperty 这些)。
理解了这条链,instanceof 就很好解释了。
原生的 instanceof 是怎么工作的?
看下面这个经典的继承例子:
js
function Animal() {}
function Person() {}
Person.prototype = new Animal();
const p = new Person();
console.log(p instanceof Person); // true
console.log(p instanceof Animal); // true
p 是 Person 的实例,它的原型链上是 Person.prototype → Animal.prototype → Object.prototype → null,所以它既是 Person 的实例,也是 Animal 的实例。
手写一个 isInstanceOf
现在我们来自己实现一个。核心思路就一句话:从 left 的 __proto__ 开始,一路往上找,看能不能找到 right.prototype。
完整代码如下(直接复制就能跑):
html
<!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 行为几乎一致。注意两点小细节:
- 我们加了个
typeof right !== 'function'的防护,防止传进来奇怪的东西报错。 - 循环结束条件是
proto变成null,这正是原型链的终点。
结合继承方式再看 instanceof
instanceof 最常出现在继承场景里。我们来对比几种常见的继承写法,看看它在每种方式下的表现。
1. 构造函数绑定继承(call/apply)
js
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 模式(推荐)
js
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(有坑)
js
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!