你以为
instanceof是个普通运算符?
不,它是 JS 世界的 “亲子鉴定中心” 。谁是谁的孩子?谁继承了谁的性格?
这一切,都藏在原型链的“家谱”里。
今天我们就来把这个八卦展开讲讲。
保证你看完后:
✔ 会手写 instanceof
✔ 懂 JS 继承到底在玩什么骚操作
✔ 再也不会被 prototype 搞懵
✔ 在大型项目不再靠“猜血统”写代码
准备好了,坐好,开扒。
🐶 一、为什么 JS 需要 instanceof:因为这个语言天生就很“乱”
其它语言(Java、C++)是这样的:
“一个类 → 一堆对象。对象天生就是这个类的孩子。”
结构稳定、血统清晰、属性固定。
而 JavaScript 呢?
对象像乐高,属性随手加随手删,构造函数能乱改,原型链还能热更新。
你永远不知道你手里的对象:
- 是不是父类生的
- 是不是某个库偷偷改过它
- 是否被代理包装
- constructor 到底是不是亲的(可以被覆盖)
- 甚至连“它到底是什么类型”都可能不确定 😅
所以 JS 需要 instanceof 帮它撑腰:
靠对象的“外表”判断不了,那就从“血缘”判断。
🧬 二、instanceof 背后的真相:它不查属性,只查“血统”
核心一句话:
A instanceof B 等价于:A 的原型链上有没有 B.prototype?
就是查家谱。
给你画个结构图(ASCII 风)👇
dog
↓ __proto__
Dog.prototype
↓ __proto__
Animal.prototype
↓ __proto__
Object.prototype
↓ __proto__
null
当你执行:
dog instanceof Animal
其实 JS 在内心默默做:
- dog.proto === Dog.prototype? ❌
- dog.proto.proto === Animal.prototype? ✅
- 哦!原来你是动物家的孩子!给你 TRUE!
这就是所有的一切。
🐾 三、先来点热身:看个原型链小实验
const arr = [];
console.log(arr.__proto__); // Array.prototype
console.log(arr.__proto__.__proto__); // Object.prototype
console.log(arr.__proto__.__proto__.__proto__); // null
所以:
arr instanceof Array // true
arr instanceof Object // true
arr instanceof Number // false
你可能会想:
“一个数组怎么还能是对象?”
因为它家谱上确实挂着 Object.prototype 啊 😂
它是“对象家的孙子”。
✍️ 四、手写一个 instanceof:三行循环就把八卦查干净
看完你会惊呼:
原来这玩意儿这么简单?!
👇 手写版本:
function isInstanceOf(left, right){
let proto = left.__proto__;
while(proto){
if(proto === right.prototype){
return true;
}
proto = proto.__proto__;
}
return false;
}
测试一下:
function 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, Array)); // false
你现在已经能写出自己的“民间亲子鉴定程序”了 😎
🐣 五、JS 的三种继承方式(影响血统判定)
来聊聊 JS 是怎么让“儿子拿到父亲的东西”的。
1)构造函数继承(call/apply)
特点:只继承属性,不继承方法。
像“代孕”,孩子跟父亲没血缘。
function Animal(){
this.type = "动物";
}
function Cat(name){
Animal.call(this);
this.name = name;
}
const cat = new Cat();
cat instanceof Animal // FALSE(没血缘)
为什么?
因为原型链上完全没有 Animal.prototype。
孩子根本不是你家的。
2)经典原型继承
function Animal(){ this.type = "动物"; }
function Cat(){}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
const cat = new Cat();
结果:
cat instanceof Cat // true
cat instanceof Animal // true
这才是正规亲子关系。
3)“危险操作”:直接把 prototype 指向父类的 prototype
Cat.prototype = Animal.prototype;
这叫啥?
“我和我爸用同一个身份证。”
你以为继承了,实际上两个构造函数共用同一个 prototype
任何一个修改都会污染全家!!
千万不要这样写。
🏢 六、为什么 instanceof 在大型项目非常重要?
多人协作的大型项目里:
- 构造函数拆成 10 个文件
- prototype 被各种库扩展
- constructor 被覆盖
- 某些对象是工厂函数返回的
- 某些是代理包装的
- 某些根本不是预期类型
你根本不知道对象到底是谁家的。
这时:
obj instanceof SomeClass
是 唯一可靠的判定方式。
不是因为它优雅——
而是因为 原型链才是唯一不会骗人的东西。
🧘 七、哲学层面理解:JS 继承是“关系型”,不是“模板型”
大部分语言是:
“你属于哪个类,就永远属于那个类。”
而 JS 是:
“你原型链连着谁,你就是谁的后代。”
继承不是一个静态关系,而是:
🟢 一个运行时可变的引用
🟢 一个对象指向另一个对象
🟢 prototype 甚至能动态修改
这就导致:
✔ 灵活
✔ 强大
✔ 也有点乱
于是 JS 官方给你了一个“八卦工具”:
instanceof:沿着原型链查血缘
🎉 八、全文总结
记住一句话就够:
A instanceof B = A 的原型链上是否出现 B.prototype?
理解这一点,你就完全懂了:
✔ 为什么要手写 instanceof
✔ 为什么某些继承方式让 instanceof 失效
✔ 为什么 constructor 不能作为可靠依据
✔ 为什么原型链才是真·亲子关系
✔ 为什么大型项目里 instanceof 非常必要
你现在已经完全掌握“JS 血缘系统”了 ✨