原来 instanceof 这么八卦?一文读懂 JS 的“原型血缘门”!

43 阅读4分钟

你以为 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 血缘系统”了 ✨