类继承
2025年3月7日大约 11 分钟
类继承
继承是面向对象编程的核心概念之一,允许创建基于现有类的新类。本文将详细介绍ES6类继承的语法、super关键字的使用以及继承链的概念,帮助您构建合理的类层次结构。
ES6类继承基础
extends关键字
在ES6中,使用extends
关键字实现类继承:
// 父类
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}发出声音`);
}
}
// 子类
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
speak() {
console.log(`${this.name}汪汪叫`);
}
fetch() {
console.log(`${this.name}在捡东西`);
}
}
const dog = new Dog('小黑', '拉布拉多');
dog.speak(); // 输出:小黑汪汪叫
dog.fetch(); // 输出:小黑在捡东西
console.log(dog.name); // 输出:小黑
console.log(dog.breed); // 输出:拉布拉多
super关键字
super
关键字有两种用法:
- 在子类构造函数中调用父类构造函数
- 在子类方法中调用父类方法
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}发出声音`);
}
}
class Dog extends Animal {
constructor(name, breed) {
// 1. 调用父类构造函数
super(name);
this.breed = breed;
}
speak() {
// 2. 调用父类方法
super.speak();
console.log(`${this.name}汪汪叫`);
}
}
const dog = new Dog('小黑', '拉布拉多');
dog.speak();
// 输出:
// 小黑发出声音
// 小黑汪汪叫
在子类构造函数中,必须先调用super()
,然后才能使用this
:
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name, breed) {
// 错误:在super()之前使用this
// this.breed = breed; // ReferenceError
super(name);
// 正确:在super()之后使用this
this.breed = breed;
}
}
如果子类没有定义构造函数,会自动生成一个构造函数,并在其中调用父类构造函数:
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
// 自动生成:constructor(...args) { super(...args); }
}
const dog = new Dog('小黑');
console.log(dog.name); // 输出:小黑
继承链与多层继承
继承链
JavaScript的继承是基于原型链的,通过extends
关键字创建的类继承也是如此:
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name}正在吃东西`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(`${this.name}汪汪叫`);
}
}
const dog = new Dog('小黑', '拉布拉多');
console.log(dog instanceof Dog); // 输出:true
console.log(dog instanceof Animal); // 输出:true
console.log(dog instanceof Object); // 输出:true
// 原型链
console.log(Object.getPrototypeOf(dog) === Dog.prototype); // 输出:true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // 输出:true
console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype); // 输出:true
多层继承
类可以形成多层继承关系:
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name}正在吃东西`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(`${this.name}汪汪叫`);
}
}
class Labrador extends Dog {
constructor(name, color) {
super(name, '拉布拉多');
this.color = color;
}
swim() {
console.log(`${this.name}在游泳`);
}
}
const labrador = new Labrador('小黄', '金黄色');
labrador.eat(); // 来自Animal类
labrador.bark(); // 来自Dog类
labrador.swim(); // 来自Labrador类
console.log(labrador.name); // 输出:小黄
console.log(labrador.breed); // 输出:拉布拉多
console.log(labrador.color); // 输出:金黄色
方法重写与扩展
方法重写
子类可以重写(覆盖)父类的方法:
class Animal {
speak() {
console.log('动物发出声音');
}
}
class Dog extends Animal {
// 重写父类方法
speak() {
console.log('汪汪汪');
}
}
class Cat extends Animal {
// 重写父类方法
speak() {
console.log('喵喵喵');
}
}
const animal = new Animal();
const dog = new Dog();
const cat = new Cat();
animal.speak(); // 输出:动物发出声音
dog.speak(); // 输出:汪汪汪
cat.speak(); // 输出:喵喵喵
方法扩展
子类可以扩展父类的方法,通过super
调用父类方法,然后添加自己的功能:
class Vehicle {
start() {
console.log('启动引擎');
}
stop() {
console.log('停止引擎');
}
}
class Car extends Vehicle {
start() {
super.start(); // 调用父类方法
console.log('检查安全带');
console.log('调整后视镜');
}
stop() {
console.log('拉起手刹');
super.stop(); // 调用父类方法
}
}
const car = new Car();
car.start();
// 输出:
// 启动引擎
// 检查安全带
// 调整后视镜
car.stop();
// 输出:
// 拉起手刹
// 停止引擎
静态方法继承
子类会继承父类的静态方法:
class MathUtils {
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
}
class AdvancedMathUtils extends MathUtils {
static subtract(a, b) {
return a - b;
}
static divide(a, b) {
if (b === 0) throw new Error('除数不能为0');
return a / b;
}
}
// 使用继承的静态方法
console.log(AdvancedMathUtils.add(5, 3)); // 输出:8
console.log(AdvancedMathUtils.multiply(5, 3)); // 输出:15
// 使用自己的静态方法
console.log(AdvancedMathUtils.subtract(5, 3)); // 输出:2
console.log(AdvancedMathUtils.divide(6, 2)); // 输出:3
继承内置类
可以继承JavaScript的内置类,如Array、Date、Error等:
// 继承Array
class MyArray extends Array {
// 添加求和方法
sum() {
return this.reduce((total, current) => total + current, 0);
}
// 添加平均值方法
average() {
if (this.length === 0) return 0;
return this.sum() / this.length;
}
}
const arr = new MyArray(1, 2, 3, 4, 5);
console.log(arr.sum()); // 输出:15
console.log(arr.average()); // 输出:3
// 继承内置方法
console.log(arr.map(x => x * 2)); // 输出:[2, 4, 6, 8, 10]
console.log(arr.filter(x => x % 2 === 0)); // 输出:[2, 4]
// 继承Error
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
toJSON() {
return {
name: this.name,
message: this.message,
field: this.field
};
}
}
try {
throw new ValidationError('用户名不能为空', 'username');
} catch (error) {
if (error instanceof ValidationError) {
console.log(error.field); // 输出:username
console.log(error.toJSON());
}
}
抽象类与接口模拟
JavaScript没有内置的抽象类和接口概念,但可以模拟实现:
模拟抽象类
// 模拟抽象类
class AbstractShape {
constructor() {
if (new.target === AbstractShape) {
throw new Error('AbstractShape是抽象类,不能直接实例化');
}
}
// 抽象方法
calculateArea() {
throw new Error('子类必须实现calculateArea方法');
}
// 具体方法
toString() {
return `面积:${this.calculateArea()}`;
}
}
class Circle extends AbstractShape {
constructor(radius) {
super();
this.radius = radius;
}
calculateArea() {
return Math.PI * this.radius * this.radius;
}
}
class Rectangle extends AbstractShape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
calculateArea() {
return this.width * this.height;
}
}
// const shape = new AbstractShape(); // 错误:AbstractShape是抽象类,不能直接实例化
const circle = new Circle(5);
const rectangle = new Rectangle(4, 6);
console.log(circle.toString()); // 输出:面积:78.53981633974483
console.log(rectangle.toString()); // 输出:面积:24
模拟接口
// 接口检查函数
function implementsInterface(obj, interfaceMethods) {
const missingMethods = interfaceMethods.filter(
method => typeof obj[method] !== 'function'
);
if (missingMethods.length > 0) {
throw new Error(
`对象缺少接口所需的方法: ${missingMethods.join(', ')}`
);
}
return true;
}
// 定义接口
const ShapeInterface = ['calculateArea', 'calculatePerimeter'];
class Square {
constructor(side) {
this.side = side;
}
calculateArea() {
return this.side * this.side;
}
calculatePerimeter() {
return 4 * this.side;
}
}
class Triangle {
constructor(a, b, c) {
this.a = a;
this.b = b;
this.c = c;
}
calculateArea() {
// 海伦公式
const s = (this.a + this.b + this.c) / 2;
return Math.sqrt(s * (s - this.a) * (s - this.b) * (s - this.c));
}
calculatePerimeter() {
return this.a + this.b + this.c;
}
}
// 检查是否实现接口
function processShape(shape) {
implementsInterface(shape, ShapeInterface);
console.log(`面积:${shape.calculateArea()}`);
console.log(`周长:${shape.calculatePerimeter()}`);
}
const square = new Square(5);
const triangle = new Triangle(3, 4, 5);
processShape(square);
// 输出:
// 面积:25
// 周长:20
processShape(triangle);
// 输出:
// 面积:6
// 周长:12
// 缺少方法的类
class InvalidShape {
constructor(radius) {
this.radius = radius;
}
calculateArea() {
return Math.PI * this.radius * this.radius;
}
// 缺少calculatePerimeter方法
}
// processShape(new InvalidShape(5)); // 错误:对象缺少接口所需的方法: calculatePerimeter
混入(Mixins)
混入是一种组合多个类功能的方式,可以解决JavaScript单继承的限制:
// 定义混入
const SpeakerMixin = {
speak(phrase) {
console.log(`${this.name}说:${phrase}`);
}
};
const SwimmerMixin = {
swim() {
console.log(`${this.name}在游泳`);
}
};
const FlyerMixin = {
fly() {
console.log(`${this.name}在飞行`);
}
};
// 应用混入
class Animal {
constructor(name) {
this.name = name;
}
}
// 将混入应用到类的原型
Object.assign(Animal.prototype, SpeakerMixin);
class Duck extends Animal {
constructor(name) {
super(name);
}
}
// 为Duck类添加多个混入
Object.assign(Duck.prototype, SwimmerMixin, FlyerMixin);
const duck = new Duck('唐老鸭');
duck.speak('嘎嘎嘎'); // 来自SpeakerMixin
duck.swim(); // 来自SwimmerMixin
duck.fly(); // 来自FlyerMixin
更优雅的混入方式是使用函数:
// 使用函数创建混入
function mixins(...mixinObjects) {
return function(targetClass) {
Object.assign(targetClass.prototype, ...mixinObjects);
return targetClass;
};
}
// 定义混入对象
const TimestampMixin = {
getCreatedAt() {
return this.createdAt;
},
setCreatedAt() {
this.createdAt = new Date();
return this;
}
};
const SerializableMixin = {
serialize() {
return JSON.stringify(this);
},
deserialize(json) {
const obj = JSON.parse(json);
Object.assign(this, obj);
return this;
}
};
// 应用混入
@mixins(TimestampMixin, SerializableMixin)
class User {
constructor(name, email) {
this.name = name;
this.email = email;
this.setCreatedAt();
}
}
// 如果不支持装饰器语法,可以这样使用
// const User = mixins(TimestampMixin, SerializableMixin)(
// class User {
// constructor(name, email) {
// this.name = name;
// this.email = email;
// this.setCreatedAt();
// }
// }
// );
const user = new User('张三', 'zhangsan@example.com');
console.log(user.getCreatedAt()); // 输出:当前日期时间
const serialized = user.serialize();
console.log(serialized); // 输出:{"name":"张三","email":"zhangsan@example.com","createdAt":"..."}
const newUser = new User('', '');
newUser.deserialize(serialized);
console.log(newUser.name); // 输出:张三
console.log(newUser.email); // 输出:zhangsan@example.com
原型继承与类继承的关系
ES6的类继承本质上是基于原型继承的语法糖:
// 使用类继承
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}发出声音`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
console.log(`${this.name}汪汪叫`);
}
}
// 等价的原型继承
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name}发出声音`);
};
function Dog(name, breed) {
// 调用父类构造函数
Animal.call(this, name);
this.breed = breed;
}
// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// 重写方法
Dog.prototype.speak = function() {
console.log(`${this.name}汪汪叫`);
};
继承的最佳实践
1. 组合优于继承
在许多情况下,组合比继承更灵活:
// 使用继承(可能导致层次结构复杂)
class Vehicle {}
class Car extends Vehicle {}
class ElectricCar extends Car {}
class SportsCar extends Car {}
class ElectricSportsCar extends SportsCar {} // 多重继承变得复杂
// 使用组合(更灵活)
class Vehicle {
constructor(engine, transmission) {
this.engine = engine;
this.transmission = transmission;
}
}
class Engine {
start() { /* ... */ }
stop() { /* ... */ }
}
class ElectricEngine extends Engine {
charge() { /* ... */ }
}
class GasolineEngine extends Engine {
refuel() { /* ... */ }
}
class Transmission {
shift() { /* ... */ }
}
// 通过组合创建不同类型的车辆
const electricCar = new Vehicle(new ElectricEngine(), new Transmission());
const gasolineCar = new Vehicle(new GasolineEngine(), new Transmission());
2. 保持继承层次浅
继承层次越深,代码越难理解和维护:
// 不推荐:深层继承
class A {}
class B extends A {}
class C extends B {}
class D extends C {}
class E extends D {}
// 推荐:扁平继承结构
class Base {}
class TypeA extends Base {}
class TypeB extends Base {}
class TypeC extends Base {}
3. 使用is-a关系判断继承合理性
继承应该表示"is-a"(是一个)关系:
// 正确:狗是一种动物
class Animal {}
class Dog extends Animal {}
// 错误:用户不是一个数据库
class Database {}
class User extends Database {} // 不合理的继承
// 正确:使用组合
class Database {
save(user) { /* ... */ }
find(id) { /* ... */ }
}
class User {
constructor(db) {
this.db = db;
}
save() {
this.db.save(this);
}
}
4. 避免构造函数中的重逻辑
子类构造函数应该主要关注初始化自己的属性,并调用父类构造函数:
// 不推荐
class Shape {
constructor(color) {
this.color = color;
// 复杂的初始化逻辑...
}
}
class Circle extends Shape {
constructor(color, radius) {
super(color);
this.radius = radius;
// 更多复杂的初始化逻辑...
}
}
// 推荐
class Shape {
constructor(color) {
this.color = color;
}
initialize() {
// 复杂的初始化逻辑...
}
}
class Circle extends Shape {
constructor(color, radius) {
super(color);
this.radius = radius;
}
initialize() {
super.initialize();
// Circle特有的初始化逻辑...
}
}
const circle = new Circle('red', 5);
circle.initialize();
实际应用示例
1. 构建UI组件库
// 基础组件
class Component {
constructor(id) {
this.element = document.getElementById(id) || document.createElement('div');
this.element.className = this.constructor.name.toLowerCase();
}
render() {
return this.element;
}
mount(parent) {
parent.appendChild(this.render());
this.onMount();
return this;
}
onMount() {
// 钩子方法,子类可以重写
}
}
// 按钮组件
class Button extends Component {
constructor(id, text, onClick) {
super(id);
this.element = document.createElement('button');
this.element.id = id;
this.element.className = 'button';
this.element.textContent = text;
this.element.addEventListener('click', onClick);
}
}
// 输入框组件
class Input extends Component {
constructor(id, placeholder, onChange) {
super(id);
this.element = document.createElement('input');
this.element.id = id;
this.element.className = 'input';
this.element.placeholder = placeholder;
this.element.addEventListener('input', onChange);
}
getValue() {
return this.element.value;
}
setValue(value) {
this.element.value = value;
return this;
}
}
// 表单组件
class Form extends Component {
constructor(id, onSubmit) {
super(id);
this.element = document.createElement('form');
this.element.id = id;
this.element.className = 'form';
this.element.addEventListener('submit', (e) => {
e.preventDefault();
onSubmit(this.getData());
});
this.fields = [];
}
addField(field) {
this.fields.push(field);
field.mount(this.element);
return this;
}
getData() {
return this.fields.reduce((data, field) => {
if (field.getValue && typeof field.getValue === 'function') {
data[field.element.id] = field.getValue();
}
return data;
}, {});
}
}
// 使用示例
const loginForm = new Form('loginForm', (data) => {
console.log('登录数据:', data);
});
const usernameInput = new Input('username', '请输入用户名', () => {});
const passwordInput = new Input('password', '请输入密码', () => {});
const loginButton = new Button('loginBtn', '登录', () => {
loginForm.element.dispatchEvent(new Event('submit'));
});
loginForm
.addField(usernameInput)
.addField(passwordInput)
.addField(loginButton)
.mount(document.body);
2. 游戏开发中的实体系统
// 游戏实体基类
class Entity {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = 0;
this.height = 0;
this.speed = 0;
}
update(deltaTime) {
// 基础更新逻辑
}
render(context) {
// 基础渲染逻辑
}
getBounds() {
return {
x: this.x,
y: this.y,
width: this.width,
height: this.height
};
}
collidesWith(entity) {
const bounds1 = this.getBounds();
const bounds2 = entity.getBounds();
return bounds1.x < bounds2.x + bounds2.width &&
bounds1.x + bounds1.width > bounds2.x &&
bounds1.y < bounds2.y + bounds2.height &&
bounds1.y + bounds1.height > bounds2.y;
}
}
// 玩家类
class Player extends Entity {
constructor(x, y) {
super(x, y);
this.width = 50;
this.height = 50;
this.speed = 200;
this.health = 100;
this.score = 0;
}
update(deltaTime) {
super.update(deltaTime);
// 玩家特有的更新逻辑
}
render(context) {
context.fillStyle = 'blue';
context.fillRect(this.x, this.y, this.width, this.height);
}
takeDamage(amount) {
this.health -= amount;
if (this.health <= 0) {
this.die();
}
}
die() {
console.log('游戏结束');
}
}
// 敌人类
class Enemy extends Entity {
constructor(x, y, type) {
super(x, y);
this.type = type;
this.width = 40;
this.height = 40;
// 根据类型设置属性
if (type === 'fast') {
this.speed = 300;
this.damage = 10;
} else if (type === 'strong') {
this.speed = 100;
this.damage = 30;
} else {
this.speed = 150;
this.damage = 20;
}
}
update(deltaTime) {
super.update(deltaTime);
// 敌人AI逻辑
this.y += this.speed * deltaTime;
}
render(context) {
context.fillStyle = this.type === 'fast' ? 'red' :
this.type === 'strong' ? 'purple' : 'orange';
context.fillRect(this.x, this.y, this.width, this.height);
}
attack(player) {
if (this.collidesWith(player)) {
player.takeDamage(this.damage);
return true;
}
return false;
}
}
// 子弹类
class Bullet extends Entity {
constructor(x, y, direction) {
super(x, y);
this.width = 10;
this.height = 10;
this.speed = 500;
this.direction = direction; // 1表示向下,-1表示向上
this.damage = 25;
}
update(deltaTime) {
super.update(deltaTime);
this.y += this.speed * this.direction * deltaTime;
}
render(context) {
context.fillStyle = 'yellow';
context.fillRect(this.x, this.y, this.width, this.height);
}
}
总结
类继承是JavaScript面向对象编程的重要特性,通过本文,我们学习了:
- ES6类继承基础:使用
extends
关键字实现继承,super
关键字调用父类构造函数和方法 - 继承链与多层继承:理解JavaScript基于原型链的继承机制,以及如何创建多层继承结构
- 方法重写与扩展:子类如何覆盖或扩展父类的方法
- 静态方法继承:子类会继承父类的静态方法
- 继承内置类:如何扩展JavaScript内置类如Array、Error等
- 抽象类与接口模拟:在JavaScript中模拟抽象类和接口
- 混入(Mixins):使用混入解决单继承的限制
- 原型继承与类继承的关系:理解ES6类继承的本质
- 继承的最佳实践:组合优于继承、保持继承层次浅、使用is-a关系判断继承合理性
- 实际应用示例:在UI组件库和游戏开发中应用类继承
掌握类继承不仅能帮助我们更好地组织代码,还能提高代码的可维护性和可扩展性。在实际开发中,应根据具体需求选择合适的继承方式,并遵循最佳实践,避免过度使用继承导致的复杂性问题。记住,组合通常比继承更灵活,特别是在需要共享多个来源的功能时。