你是否也曾被
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 是关键先生:
- 创建一个空对象
{}; - 把
this指向它; - 执行函数体,给它加上名字和颜色;
- 返回这个新对象。
✅ 就像开了家“猫咪制造厂”,输入参数,输出定制猫。
💡 三、问题来了:每只猫都会“吃千层面”吗?
假设所有猫都有共同爱好:喜欢吃 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
⚠️ 虽然强大,但不建议随意修改原生对象原型,容易引发冲突!
📚 八、终极图解:一张图看懂原型关系
📌 核心要点:
- 每个函数都有
prototype属性; - 每个对象都有
__proto__指向其原型; - 原型对象有
constructor指回构造函数; - 查找属性时走原型链,直到
null。
✅ 九、总结:JS 面向对象的三大支柱
| 概念 | 作用 | 类比 |
|---|---|---|
constructor | 构造实例,初始化私有属性 | 工厂流水线 |
prototype | 共享方法与属性 | 家族传承秘籍 |
__proto__ | 连接原型链,实现继承 | 血缘关系网 |
🧩 JavaScript 的面向对象,不是“血统论”,而是“师徒制”。
没有天生贵族,只有不断学习和传承。
🎯 写在最后:你真的懂你的“猫”了吗?
下一次当你写下:
class Cat { ... }
请记得,在背后默默工作的,是那个默默无闻却无比强大的 原型系统。
它不像类那样规整,但却足够灵活、动态、富有生命力——正如 JavaScript 本身。
❤️ 如果你觉得这篇文章帮你理清了思路,请点赞 + 收藏 + 分享!
让更多人告别“原型恐惧症”,拥抱真正的 JavaScript 之美!
#JavaScript #前端开发 #原型链 #面向对象 #原型模式 #掘金新手村 #面试必备 #JS深入浅出