🔥 从“小猫老弟”到 class:JavaScript 面向对象的 5 次进化,90% 的人卡在原型链!
“为什么我写了 1000 个
Cat,内存爆了?”
“instanceof为啥认不出我的猫?”
“class到底是不是 JavaScript 的‘类’?”
如果你也曾在这些坑里打转,那你不是一个人。
JavaScript 的面向对象,是这门语言最被误解的机制——它没有类,却能模拟一切;它靠原型,却让你写得像 Java。
今天,我们就用你熟悉的那只 “小猫老弟” 和 “猫泪” ,一步步穿越 JS OOP 的 5 次关键进化,看清每一步解决了什么,又埋下了什么雷。
更重要的是:你会彻底明白,为什么 ES6 的 class 只是“语法糖”,而原型才是 JavaScript 的灵魂。
🧱 第 1 步:原始模式 —— “小猫老弟”的诞生
// 定义一个猫的模板对象
var Cat = {
name: "",
color: ""
};
// 创建实例
var cat1 = {};
cat1.name = "小猫老弟";
cat1.color = "红色";
var cat2 = {};
cat2.name = "猫泪";
cat2.color = "蓝色";
看起来没问题?但当你需要第 3 只、第 100 只猫时——
复制粘贴地狱开启。
❌ 三大硬伤:
- 代码重复严重:每个属性都要手动赋值
- 对象毫无血缘关系:
cat1 instanceof Cat?报错! - 无法统一管理:想加个
eat()方法?每只猫都得写一遍!
💡 这种写法,只适合一次性配置对象。
一旦涉及“批量生产”,立刻崩盘。
⚙️ 第 2 步:构造函数 —— 终于能“生猫”了!
function Cat(name, color) {
this.name = name;
this.color = color;
}
const cat1 = new Cat('小猫老弟', '红色');
const cat2 = new Cat('猫泪', '蓝色');
console.log(cat1 instanceof Cat); // true ✅
console.log(cat2.constructor === cat1.constructor); // true ✅
进步巨大:
- 批量创建 ✔️
- 类型可识别 ✔️
- 初始化逻辑集中 ✔️
但!当你给猫加上吃饭技能:
function Cat(name, color) {
this.name = name;
this.color = color;
this.eat = function() {
console.log('吃饭');
}
}
💥 灾难来了:
每只猫都自带一份 eat 函数!
1000 只猫 → 1000 份相同代码 → 内存浪费到离谱!
📉 这不是面向对象,这是“面向内存泄漏”。
🔗 第 3 步:原型模式 —— 所有猫共享一碗饭!
function Cat(name, color) {
this.name = name;
this.color = color;
}
// 将共享方法定义在原型上
Cat.prototype.type = '猫';
Cat.prototype.eat = function() {
console.log('吃饭');
};
现在:
const cat1 = new Cat('小猫老弟', '红色');
const cat2 = new Cat('猫泪', '蓝色');
cat1.eat(); // 吃饭
console.log(cat1.type, cat2.type); // 猫 猫
✅ 所有实例共享同一份 eat 方法 → 内存省了 99%!
✅ 修改 Cat.prototype.eat,所有猫立刻学会新吃法!
⚠️ 但注意“属性遮蔽”:
cat1.type = '哈吉米';
console.log(cat1.type, cat2.type); // 哈吉米 猫
🧠 原型不是“父类”,而是“公共厨房” 。
实例自己有饭?吃自己的。没有?去厨房拿。
🧬 第 4 步:继承 —— 让猫成为“动物”
我们想让猫继承自 Animal:
function Animal() {
this.species = '动物';
}
function Cat(name, color) {
Animal.apply(this); // 继承属性
this.name = name;
this.color = color;
}
✅ cat.species 能用了!
❌ 但 cat.sayHi() 不行!因为原型方法没继承。
✅ 完整方案:原型链继承
function Animal() {
this.species = '动物';
}
Animal.prototype.sayHi = function() {
console.log('hi');
};
function Cat(name, color) {
Animal.apply(this);
this.name = name;
this.color = color;
}
// 关键一步:建立原型链
Cat.prototype = new Animal();
const cat = new Cat('小猫老弟', '红色');
cat.sayHi(); // hi ✅
效果完美,但写法繁琐、逻辑绕弯。
开发者内心 OS: “能不能别让我手动搭原型链?!”
✨ 第 5 步:ES6 class —— 优雅封装,底层仍是原型
class Animal {
constructor() {
this.species = '动物';
}
sayHi() {
console.log('hi');
}
}
class Cat extends Animal {
constructor(name, color) {
super(); // 调用父类构造函数
this.name = name;
this.color = color;
}
}
const cat = new Cat('小猫老弟', '红色');
cat.sayHi(); // hi ✅
看起来像 Java,用起来像 Python,底层还是 JavaScript。
🔍 它到底是不是“新东西”?
不是!它只是语法糖。上面的 class 等价于:
function Animal() { this.species = '动物'; }
Animal.prototype.sayHi = function() { console.log('hi'); };
function Cat(name, color) {
Animal.call(this);
this.name = name;
this.color = color;
}
Object.setPrototypeOf(Cat.prototype, Animal.prototype);
✅
class自动做了三件事:
- 把方法挂到
prototype上- 用
extends设置原型链- 用
super()调用父构造函数
但它更安全:
- 不能无
new调用 - 方法不可枚举(避免
for...in污染) - 不可提升(防止误用)
📊 演进全景图:JS OOP 的 5 次跃迁
| 阶段 | 核心方案 | 解决的问题 | 新痛点 | 推荐度 |
|---|---|---|---|---|
| 1️⃣ 原始模式 | 对象字面量 | 快速创建单个对象 | 无法复用、无类型 | ❌ |
| 2️⃣ 构造函数 | function + new | 批量创建 + 类型识别 | 方法重复,内存爆炸 | ⚠️ |
| 3️⃣ 原型模式 | Constructor.prototype | 方法共享,节省内存 | 继承写法复杂 | ✅(必懂) |
| 4️⃣ 原型链继承 | apply + new Parent() | 完整继承属性与方法 | 冗余调用、代码啰嗦 | ⚠️ |
| 5️⃣ ES6 class | class + extends | 语法简洁、安全可靠 | 隐藏底层机制 | ✅(日常首选) |
💡 终极结论:糖要吃,发动机更要懂
class是方向盘,原型才是发动机。
你可以用 class 写出优雅代码,
但一旦遇到:
this指向异常super行为诡异- 动态修改方法失效
不懂原型,你就只能猜、只能试、只能 Stack Overflow。
而懂原型的人,一眼看穿本质。
🎯 结语:从小猫老弟,到 JS 高手
那只叫“小猫老弟”的红色猫咪,
不仅教会我们如何创建对象,
更带我们走过了 JavaScript 面向对象的完整进化史。
🌟 记住:
会用class,是合格的前端;
懂得原型,才是真正的 JavaScript 工程师。
🔔 如果你觉得有收获,请:
- ❤️ 点赞 / 收藏(让更多“猫主子”看到)
- 💬 评论区留言:“我的猫叫______,它会______”
- 🔗 转发给那个还在手动写 1000 份
eat函数的朋友