JavaScript继承:别让老爸的咖啡杯被你喝空了!
🎭 一个小故事
小明想"继承"他老爸的咖啡杯,于是写了:
Son.prototype = Father.prototype;
结果?他老爸刚倒完咖啡,小明的杯子也空了!
为什么?因为小明继承的不是咖啡杯的"副本",而是"同一个杯子"!
核心问题:JavaScript的继承是"引用",不是"复制"!
🔥 三种继承方式,只有一种能用(副作用大揭秘)
❌ 1. 直接继承原型(最坑!)
function Father() {}
Father.prototype.coffee = "星巴克";
function Son() {}
Son.prototype = Father.prototype; // 直接继承
// 修改Son的原型
Son.prototype.coffee = "麦当劳";
console.log(Father.prototype.coffee); // 输出: "麦当劳" ❌ !!
副作用:修改Son的原型,Father的原型也被改了!
就像小明的"继承" :老爸刚倒完咖啡,小明的杯子也空了——因为它们根本是同一个杯子!
⚠️ 2. 组合继承(有小坑)
function Father(name) {
this.name = name;
}
Father.prototype.sayHello = function() { console.log("Hello " + this.name); };
function Son(name, age) {
Father.call(this, name); // 借用构造函数
this.age = age;
}
Son.prototype = new Father(); // 原型链继承
Son.prototype.constructor = Son;
// 父类构造函数被调用了两次!
副作用:父类构造函数被调用了两次,浪费性能!
就像小明:老爸先给他倒了杯咖啡(第一次调用),然后又给他整了个新杯子(第二次调用)——多喝了一杯,浪费!
📹小剧场
小明:老爸,什么是原型啊?
老爸:原型都不知道?还不去看你晴栀哥的文章!(朝着小明甩了一个链接)
✅ 3. 寄生组合继承(最优方案)
💡 为什么它这么牛?
- 用空函数当"中间人"
- 不污染父类原型
- 不重复调用父类构造函数
function inherit(child, parent) {
const temp = function() {}; // 空函数!
temp.prototype = parent.prototype;
child.prototype = new temp();
child.prototype.constructor = child;
}
🔍 关键原理(不污染父类的秘诀)
temp是空函数,它的实例不会影响temp的原型temp.prototype = parent.prototype→ 让中介指向父类原型child.prototype = new temp()→ 子类原型 = 中介实例- 所以:
child.__proto__→child.prototype→parent.prototype→Object.prototype
副作用?不存在的! 父类原型一点没被污染。
💼 面试官问:"手写继承"?直接甩出这个:
function inherit(child, parent) {
const temp = function() {};
temp.prototype = parent.prototype;
child.prototype = new temp();
child.prototype.constructor = child;
}
为什么这是标准答案?
- 解决了直接继承的副作用(不污染父类)
- 避免了组合继承的重复调用
- 是目前最优雅、最常用的继承方式
🧠 一句话总结继承哲学
| 继承方式 | 像什么 | 问题 | 解决方案 |
|---|---|---|---|
| 直接继承 | 借了老爸的咖啡杯 | 老爸倒咖啡,你杯子空了 | ❌ 不能用 |
| 组合继承 | 老爸倒了两杯咖啡 | 多喝了一杯,浪费! | ⚠️ 有小坑 |
| 空函数寄生组合继承 | 用空杯子当中介,完美复制 | 不污染老爸的杯子! | ✅ 面试必用! |
💡 小明的顿悟:
"继承不是复制,而是优雅的引用。用空函数寄生组合继承,我既能拥有老爸的咖啡杯,又不会让他喝不到咖啡!"
现在,轮到你了——面试官问"手写继承",你直接甩出这个代码,然后说:
"这个方案解决了直接继承的副作用,避免了组合继承的重复调用,是目前最优雅的继承方式。"