在 JavaScript 的面向对象世界里,常把“子类”比作“儿子”,“父类”比作“父亲”。
那么问题来了:儿子继承了父亲的基因(属性和方法),但我们希望给儿子加点新本事(比如会弹吉他),又不想动父亲一根毫毛——这能做到吗?
答案是:不仅能,而且必须这么做! 否则一不小心,就可能让“父亲”突然开始跳街舞,全家乱套。
🧬 一、错误示范:直接篡改“父亲的基因”
js
编辑
function Father() {}
Father.prototype.say = function() { console.log("我是爸爸"); };
function Son() {}
// 危险操作 ❌
Son.prototype = Father.prototype; // 儿子直接共用父亲的原型
// 给儿子加技能
Son.prototype.playGuitar = function() { console.log("儿子会弹吉他"); };
// 结果?
const dad = new Father();
dad.playGuitar(); // "儿子会弹吉他" —— 爸爸居然也会弹吉他?!
💥 问题本质:
Son.prototype和Father.prototype指向同一个对象。
修改儿子的原型,等于直接修改父亲的原型——继承变成了“共享” ,完全违背封装原则。
🛡️ 二、正确姿势:用“中介”隔离父子基因
我们引入一个空的中介函数 F,让它成为父子之间的“安全桥梁”:
js
编辑
function inherit(Child, Parent) {
const F = function() {}; // 中介:一个干净的空壳
F.prototype = Parent.prototype; // 让中介的原型指向父亲
Child.prototype = new F(); // 儿子的原型 = 中介的实例
Child.prototype.constructor = Child; // 修复 constructor
}
🔍 这样做的效果:
Son.prototype.__proto__ === Father.prototype✅(能继承父亲的方法)Son.prototype !== Father.prototype✅(修改儿子不会影响父亲)- 所有新增方法只属于儿子
js
编辑
inherit(Son, Father);
Son.prototype.playGuitar = function() { console.log("儿子会弹吉他"); };
new Father().playGuitar(); // ❌ TypeError: not a function
new Son().say(); // ✅ "我是爸爸"
new Son().playGuitar(); // ✅ "儿子会弹吉他"
✅ 完美隔离:儿子在自己的“房间”里折腾,父亲在隔壁安然无恙。
🧠 三、现代写法:Object.create() 更简洁
ES5 之后,我们可以不用手动写 F 函数:
js
编辑
Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor = Son;
Object.create(proto) 的作用就是:创建一个新对象,其 __proto__ 指向 proto,但自身是空的——这正是我们需要的“安全中介”。
🏗️ 四、完整最佳实践:寄生组合继承
既要继承属性(通过 call),又要安全继承方法(通过原型隔离):
js
编辑
function Father(name) {
this.name = name;
}
Father.prototype.say = function() { console.log(`我是${this.name}`); };
function Son(name, hobby) {
Father.call(this, name); // 继承属性(不共享)
this.hobby = hobby;
}
// 安全继承方法
Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor = Son;
// 儿子专属方法
Son.prototype.showHobby = function() { console.log(`我喜欢${this.hobby}`); };
现在:
- 每个
Son实例都有独立的name和hobby say()方法来自父亲,但调用时this正确绑定到儿子- 给
Son.prototype加任何方法,都不会污染Father
💡 五、总结:继承的黄金法则
“继承不是复制,而是建立一条安全、单向、可扩展的链。”
- ✅ 要改儿子? → 只动
Son.prototype - ❌ 别碰父亲! → 永远不要让子类原型直接等于父类原型
- 🔒 用
Object.create(Parent.prototype)建立隔离层 - 🧬 属性用
call,方法靠原型链
这样,儿子既能站在父亲的肩膀上成长,又不会把父亲的书房搞得一团糟——这才是真正的“孝顺式继承”。
🌟 记住:在 JavaScript 中,好的继承,是让改变只发生在该发生的地方。
不修改父亲,却能让儿子更强大——这不仅是编程智慧,也是生活哲学。