嘿,大家好!今天咱们聊聊JavaScript里继承 (extends
) 和多态这对好基友,看看它们怎么帮我们构建更牛掰的类结构。放心,咱们不搞那些故弄玄虚的理论,直接上代码,用大白话把这俩概念给整明白。
第一幕:继承——“爸”业我来“承”!
想象一下,你老爸是个大老板,家产万贯。继承,就跟你继承你老爸的财产一样,子类继承父类的属性和方法。这样,你不用从头开始,站在巨人的肩膀上,效率嗖嗖地!
在JS里,extends
关键字就是干这个的。
// 定义一个“老爸”类
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} 在吃东西呢!`);
}
sayHello() {
console.log("大家好,我是动物!");
}
}
// 定义一个“儿子”类,继承 Animal
class Dog extends Animal {
constructor(name, breed) {
// 调用父类的 constructor
super(name);
this.breed = breed;
}
bark() {
console.log("汪汪汪!");
}
}
// 创建一个 Dog 的实例
const myDog = new Dog("旺财", "中华田园犬");
// 调用父类的 eat 方法
myDog.eat(); // 输出:旺财 在吃东西呢!
// 调用子类自己的 bark 方法
myDog.bark(); // 输出:汪汪汪!
// 调用父类的 sayHello 方法
myDog.sayHello(); // 输出:大家好,我是动物!
这段代码里,Animal
是父类(也被称为基类或超类),Dog
是子类(也被称为派生类)。Dog
通过 extends Animal
继承了 Animal
的所有属性和方法。
super()
的作用:super()
就像一个电话,告诉父类的构造函数:“喂,老爸,该你出场了!” 它负责调用父类的constructor
,确保父类的初始化逻辑也能执行。 如果子类有自己的构造函数,就必须先调用super()
,才能使用this
。
继承的优势:
- 代码重用: 子类可以直接使用父类的属性和方法,避免重复编写代码。
- 可维护性: 如果父类有 bug,只需要修改父类的代码,所有子类都会受到影响,易于维护。
- 可扩展性: 可以通过继承来创建新的类,扩展现有类的功能。
第二幕:多态——“变脸”大法!
多态,顾名思义,就是“多种形态”。同一个方法,在不同的对象上,可以有不同的行为。就像孙悟空的七十二变,想变啥样就变啥样。
多态主要有两种实现方式:
- 重写 (Override): 子类重写父类的方法,改变方法的行为。
- 接口 (Interface) 和抽象类 (Abstract Class) (JS里没有严格的接口,但可以用鸭子类型模拟): 定义一套规范,不同的类实现这些规范,从而实现多态。
1. 重写 (Override):
// 还是 Animal 类
class Animal {
constructor(name) {
this.name = name;
}
makeSound() {
console.log("动物发出声音");
}
}
// Dog 类继承 Animal
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
// 重写父类的 makeSound 方法
makeSound() {
console.log("汪汪汪!");
}
}
// Cat 类继承 Animal
class Cat extends Animal {
constructor(name, color) {
super(name);
this.color = color;
}
// 重写父类的 makeSound 方法
makeSound() {
console.log("喵喵喵!");
}
}
// 创建实例
const animal = new Animal("普通动物");
const dog = new Dog("旺财", "中华田园犬");
const cat = new Cat("咪咪", "白色");
animal.makeSound(); // 输出:动物发出声音
dog.makeSound(); // 输出:汪汪汪!
cat.makeSound(); // 输出:喵喵喵!
在这个例子里,Dog
和 Cat
都继承了 Animal
的 makeSound
方法,但是它们都重写了这个方法,让它们发出不同的声音。这就是多态的体现。
2. 接口 (Interface) 和抽象类 (Abstract Class) (模拟):
虽然 JavaScript 没有像 Java 或 C# 那样的接口和抽象类,但我们可以用“鸭子类型”来模拟。 鸭子类型的意思是:“如果它走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子。”
// 定义一个行为规范(类似接口)
const Swimmable = {
swim: function() {
throw new Error("必须实现 swim 方法");
}
};
// 实现 Swimmable 规范的 Duck 类
class Duck {
constructor(name) {
this.name = name;
}
swim() {
console.log(`${this.name} 在游泳!`);
}
}
// 实现 Swimmable 规范的 Fish 类
class Fish {
constructor(name) {
this.name = name;
}
swim() {
console.log(`${this.name} 在水里游来游去!`);
}
}
// 一个不会游泳的 Bird 类
class Bird {
constructor(name) {
this.name = name;
}
fly() {
console.log(`${this.name} 在飞!`);
}
}
// 检查对象是否实现了 Swimmable 规范
function isSwimmable(obj) {
return typeof obj.swim === 'function';
}
const duck = new Duck("唐老鸭");
const fish = new Fish("小鲤鱼");
const bird = new Bird("小鸟");
if (isSwimmable(duck)) {
duck.swim(); // 输出:唐老鸭 在游泳!
}
if (isSwimmable(fish)) {
fish.swim(); // 输出:小鲤鱼 在水里游来游去!
}
if (isSwimmable(bird)) {
bird.swim(); // 不会执行,因为 Bird 没有 swim 方法
} else {
console.log(`${bird.name} 不会游泳。`); // 输出:小鸟 不会游泳。
}
在这个例子里,Swimmable
就像一个接口,定义了 swim
方法。Duck
和 Fish
都实现了这个接口,所以它们都可以游泳。Bird
没有实现这个接口,所以它不能游泳。
多态的优势:
- 灵活性: 可以根据对象的类型来执行不同的操作,提高了代码的灵活性。
- 可扩展性: 可以很容易地添加新的类,而不需要修改现有的代码。
- 可维护性: 代码更加模块化,易于维护。
第三幕:继承与多态的结合——打造强大的类结构!
继承和多态通常一起使用,可以构建非常强大的类结构。
// 定义一个 Shape 类(抽象类)
class Shape {
constructor(color) {
this.color = color;
if (new.target === Shape) {
throw new Error("抽象类不能实例化!");
}
}
// 定义一个抽象方法,子类必须实现
getArea() {
throw new Error("必须实现 getArea 方法");
}
draw() {
console.log(`绘制一个 ${this.color} 的形状`);
}
}
// 定义一个 Circle 类,继承 Shape
class Circle extends Shape {
constructor(color, radius) {
super(color);
this.radius = radius;
}
// 实现 getArea 方法
getArea() {
return Math.PI * this.radius * this.radius;
}
draw() {
console.log(`绘制一个 ${this.color} 的圆形,半径为 ${this.radius}`);
}
}
// 定义一个 Rectangle 类,继承 Shape
class Rectangle extends Shape {
constructor(color, width, height) {
super(color);
this.width = width;
this.height = height;
}
// 实现 getArea 方法
getArea() {
return this.width * this.height;
}
draw() {
console.log(`绘制一个 ${this.color} 的矩形,宽为 ${this.width},高为 ${this.height}`);
}
}
// 创建实例
// const shape = new Shape("红色"); // 报错:抽象类不能实例化!
const circle = new Circle("蓝色", 5);
const rectangle = new Rectangle("绿色", 4, 6);
console.log(`圆形的面积:${circle.getArea()}`); // 输出:圆形的面积:78.53981633974483
console.log(`矩形的面积:${rectangle.getArea()}`); // 输出:矩形的面积:24
circle.draw(); // 输出:绘制一个 蓝色 的圆形,半径为 5
rectangle.draw(); // 输出:绘制一个 绿色 的矩形,宽为 4,高为 6
// 多态的体现
function printArea(shape) {
console.log(`这个形状的面积是:${shape.getArea()}`);
}
printArea(circle); // 输出:这个形状的面积是:78.53981633974483
printArea(rectangle); // 输出:这个形状的面积是:24
在这个例子里:
Shape
是一个抽象类,定义了getArea
抽象方法和draw
普通方法。 抽象类不能被直接实例化,只能被继承。getArea
方法强制子类必须实现,否则会报错。Circle
和Rectangle
继承了Shape
,并实现了getArea
方法。printArea
函数接受一个Shape
类型的参数,它可以接受任何Shape
的子类的实例,并调用它们的getArea
方法。 这就是多态的体现。
总结:
特性 | 继承 (extends ) |
多态 |
---|---|---|
目的 | 代码重用,构建类层次结构 | 灵活性,允许不同类型的对象对同一消息做出不同的响应 |
关键字 | extends , super |
重写 (Override), 接口 (Interface) (鸭子类型模拟) |
优点 | 减少代码冗余,提高可维护性,易于扩展 | 提高代码的灵活性,可扩展性,可维护性 |
适用场景 | 当多个类具有相似的属性和方法时,可以使用继承。 | 当需要根据对象的类型来执行不同的操作时,可以使用多态。 |
例子 | Dog extends Animal (Dog 继承 Animal 的属性和方法) |
Animal 和 Dog 都有 makeSound 方法,但它们的实现不同 (重写) |
注意事项:
- 避免过度继承: 继承层次过深会使代码变得复杂难以理解和维护。
- 优先使用组合: 在某些情况下,组合比继承更灵活。 组合是指在一个类中使用另一个类的实例作为成员变量。
- 理解
this
的指向: 在继承和多态中,this
的指向可能会让你感到困惑。 需要仔细理解this
的绑定规则。
总的来说,继承和多态是面向对象编程的两大基石。 掌握它们,可以让你写出更优雅、更健壮、更易于维护的代码。 希望今天的讲座对你有所帮助! 下次有机会再跟大家分享其他编程技巧!拜拜!