子类继承父类,怎么在不修改父类的情况下来修改子类?——深入理解 JavaScript 原型继承的安全隔离机制

45 阅读2分钟

在 JavaScript 的面向对象世界里,常把“子类”比作“儿子”,“父类”比作“父亲”。
那么问题来了:儿子继承了父亲的基因(属性和方法),但我们希望给儿子加点新本事(比如会弹吉他),又不想动父亲一根毫毛——这能做到吗?

image.png 答案是:不仅能,而且必须这么做! 否则一不小心,就可能让“父亲”突然开始跳街舞,全家乱套。


🧬 一、错误示范:直接篡改“父亲的基因”

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.prototypeFather.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,方法靠原型链

这样,儿子既能站在父亲的肩膀上成长,又不会把父亲的书房搞得一团糟——这才是真正的“孝顺式继承”。


image.png

🌟 记住:在 JavaScript 中,好的继承,是让改变只发生在该发生的地方
不修改父亲,却能让儿子更强大——这不仅是编程智慧,也是生活哲学。