🐱从一只猫开始,讲透JavaScript原型:原来JS是这样“面向对象”的!

44 阅读4分钟

你是否也曾被 prototype__proto__constructor 绕得头晕眼花?
是否觉得 ES6 的 class 只是语法糖,但底层依然迷雾重重?
别怕!今天我们就用一只橘色的加菲猫,带你彻底搞懂 JavaScript 的原型机制和面向对象编程本质。


🐾 一、没有类的年代,我们怎么造“猫”?

在 Java 或 Python 中,定义一个“猫”轻而易举:

class Cat {
    String name;
    String color;
}

但在早期的 JavaScript 里——class 关键字都没有!

那怎么办?只能靠“手工打造”一个个对象:

var cat1 = {};
cat1.name = '加菲猫';
cat1.color = '橘色';

var cat2 = {};
cat2.name = '小白猫';
cat2.color = '白色';

👉 看起来没问题?可如果全世界有 1000 只猫呢?你要写 1000 遍重复代码吗?这就像每天手工捏泥人,效率低还容易出错。


🔨 二、构造函数:批量生产“猫”的工厂

聪明的程序员想到了:封装一个造猫机器!

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

const cat1 = new Cat('加菲猫', '橘色');
const cat2 = new Cat('小白猫', '白色');

✨ 这里的 new 是关键先生:

  1. 创建一个空对象 {}
  2. this 指向它;
  3. 执行函数体,给它加上名字和颜色;
  4. 返回这个新对象。

✅ 就像开了家“猫咪制造厂”,输入参数,输出定制猫。


💡 三、问题来了:每只猫都会“吃千层面”吗?

假设所有猫都有共同爱好:喜欢吃 Jerry(别问为什么)。

你会怎么写?

❌ 方式一:笨办法 —— 每次都新建方法

function BadCat(name) {
    this.name = name;
    this.eat = function() {
        console.log('我在吃Jerry!');
    };
}

⚠️ 危险操作!每只猫都自带一份 eat 函数,内存爆炸预警!

相当于给每个员工发一台复印机,只为打印同一张通知单……

✅ 正确姿势:使用 prototype —— 共享技能库!

Cat.prototype.type = '猫科动物';
Cat.prototype.eat = function() {
    console.log('喜欢jerry');
};

🎉 现在,cat1.eat()cat2.eat() 调用的是同一个函数

🧠 内存省了,代码优雅了,关键是:动态可扩展!

// 随时给所有猫增加新技能
Cat.prototype.sleep = function() {
    console.log(this.name + '正在打呼噜...');
};

cat1.sleep(); // 加菲猫正在打呼噜...

💡 原型就像是家族传承秘籍,后代天生就会!


🔗 四、深入核心:什么是原型链?—— 找不到就往上问爹!

当你调用 cat1.eat() 时,JS 是怎么找到这个方法的?

🔍 查找流程如下:

cat1 
→ 自己身上找 eat?没找到 → 去 __proto__ 找
→ Cat.prototype 上找到了!执行

Cat.prototype 本身也是一个对象,它的 __proto__ 指向哪?

👉 Object.prototype

再往上?Object.prototype.__proto__ === null —— 到头了。

这就是所谓的 原型链(Prototype Chain)

cat1 
→ __proto__ (Cat.prototype)
→ __proto__ (Object.prototype)
→ __proto__ (null) ❌ 停止查找

🎯 总结一句话:

对象自己不会的,就去原型那里学;原型不会的,就问它爹;一直问到“祖宗”为止。

🎉 五、ES6 来了!class 是语法糖,但更甜了!

终于,ES6 给我们带来了熟悉的 class 写法:

class Cat {
    constructor(name, color) {
        this.name = name;
        this.color = color;
    }
    eat() {
        console.log('喜欢jerry');
    }
}

const a = new Cat('加菲猫', '橘色');
console.log(a); // {name: "加菲猫", color: "橘色"}
a.eat();        // 喜欢jerry

👏 写法清爽多了!但注意:这只是语法糖,底层依然是原型机制。

你可以验证:

typeof Cat           // "function"
Cat.prototype.eat    // 存在
a.__proto__ === Cat.prototype  // true

💬 所以说:class 是披着类外衣的原型娃,骨子里还是那个灵活自由的 JS。


🧠 六、几个必须掌握的小技巧

1. 判断属性在哪?

a.hasOwnProperty('name')   // true → 实例自己的
a.hasOwnProperty('eat')      // false → 在原型上

'name' in a                 // true
'type' in a                 // true(不管在哪,只要能访问到)

2. 遍历所有可枚举属性

for (let key in a) {
    console.log(key); // name, color, eat...
}

3. 检查是不是某个类的实例

a instanceof Cat     // true
a instanceof Object  // true(所有对象都是 Object 的后代)

🌟 七、为什么 JS 要设计“原型”而不是“类”?

这是 JS 设计哲学的核心之一!

特性说明
动态性可以随时修改原型,影响所有已有实例
灵活性不需要提前定义类结构,运行时也能扩展
轻量高效方法共享,节省内存

🌰 举个例子:

Array.prototype.last = function() {
    return this[this.length - 1];
};

[1, 2, 3].last(); // 3

⚠️ 虽然强大,但不建议随意修改原生对象原型,容易引发冲突!


📚 八、终极图解:一张图看懂原型关系

PixPin_2025-12-24_14-21-16.png

📌 核心要点:

  • 每个函数都有 prototype 属性;
  • 每个对象都有 __proto__ 指向其原型;
  • 原型对象有 constructor 指回构造函数;
  • 查找属性时走原型链,直到 null

✅ 九、总结:JS 面向对象的三大支柱

概念作用类比
constructor构造实例,初始化私有属性工厂流水线
prototype共享方法与属性家族传承秘籍
__proto__连接原型链,实现继承血缘关系网

🧩 JavaScript 的面向对象,不是“血统论”,而是“师徒制”。
没有天生贵族,只有不断学习和传承。


🎯 写在最后:你真的懂你的“猫”了吗?

下一次当你写下:

class Cat { ... }

请记得,在背后默默工作的,是那个默默无闻却无比强大的 原型系统

它不像类那样规整,但却足够灵活、动态、富有生命力——正如 JavaScript 本身。


❤️ 如果你觉得这篇文章帮你理清了思路,请点赞 + 收藏 + 分享!

让更多人告别“原型恐惧症”,拥抱真正的 JavaScript 之美!

#JavaScript #前端开发 #原型链 #面向对象 #原型模式 #掘金新手村 #面试必备 #JS深入浅出