龙生龙、凤生凤,老鼠的孩子会打洞。
继承是面向对象编程的四大基本特征之一(继承、封装、多态和抽象),它遵循 里氏替换原则
,允许子类替换父类而不影响程序的正确性。
多种继承方式
原型继承
在理解原型链继承之前,先了解一下 JavaScript 中的对象引用和方法调用。
js
const o = {
name: 'Alice',
age: 20,
sayHello() {
console.log(`Hello, my name is ${this.name}`);
}
};
const p = Object.create(o);
原型链继承的本质是对象引用和方法调用。通过对象引用的方式实现属性和方法共享,通过方法调用的方式改变 this 指向。
js
function Person(name) {
this.name = name;
}
Person.prototype.speak = function() {
console.log(`${this.name} said "How are you?".`);
}
function Student(name) {
// 调用父类构造函数,可以为父类构造函数传参
Person.call(this, name);
}
// 因为方法都在原型上,所以我们可以直接让 Student 的原型指向 Person 的原型
Student.prototype = Object.create(Person.prototype);
// 确保 Student 的构造函数指向 Student(修复 this 指向问题)
Student.prototype.constructor = Student;
const student = new Student('John');
student.speak();
类继承
ES6 引入的 class 和 extends 关键字,提供更简洁的语法糖,本质上仍是基于原型的继承。
js
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // 调用父类构造函数
}
speak() {
console.log(`${this.name} barks.`);
}
}
const dog = new Dog('Rex');
dog.speak(); // 输出: Rex barks.
原型继承 vs 类继承
特性 | 原型继承 | 类继承 |
---|---|---|
语法 | 基于对象和 prototype 属性 | 基于 class 和 extends 关键字 |
可维护性 | 相对较难维护 | 更易读和维护 |
多重继承 | 需要手动实现 | 不直接支持 |
框架支持 | 传统方式 | 现代框架常用方式 |
继承的优缺点
优点
- 代码复用:子类可以复用父类的方法
- 层次清晰:形成类的层次结构
- 易于扩展:新增子类不影响已有代码
缺点
- 父类变更影响子类
- 不同实现方式可能导致混乱
- 继承层次过深会影响性能
使用继承的最佳实践
- 优先考虑组合而非继承
- 明确继承关系的"is-a"语义
- 保持继承层次简洁
- 注意继承中的属性共享问题
- 了解并合理使用 ES6 类的继承语法糖