Class 的 `extends` 关键字与 `super` 关键字原理

好的,各位观众老爷,各位编程界的弄潮儿,欢迎来到“JavaScript进阶奇妙夜”!今晚,我们不聊八卦,不谈风月,只聊聊JavaScript中一对既神秘又亲切的好基友:extendssuper

准备好了吗?系好安全带,我们要起飞咯!🚀

第一幕:继承的诱惑——为何需要 extends

想象一下,你是一个建筑设计师,你已经设计了一款非常棒的“标准公寓”蓝图,包含了客厅、卧室、厨房等基本功能。现在,你的客户想让你设计一款“豪华公寓”,它在“标准公寓”的基础上,还需要一个游泳池、一个私人影院和一个屋顶花园。

你会怎么做?难道要从零开始,重新画一份完整的蓝图吗?那也太傻了吧!🤯

聪明的做法是:

  1. 复制“标准公寓”的蓝图。
  2. 修改复制后的蓝图,添加游泳池、私人影院和屋顶花园。

这种“复制并修改”的思想,就是继承的核心思想。在编程世界里,extends 关键字就是那个帮你复制蓝图的神奇工具!

extends 的作用:

  • 建立父子关系: 它告诉 JavaScript 引擎:“豪华公寓” 继承 自 “标准公寓”。“标准公寓”是 父类 (Parent Class),而“豪华公寓”是 子类 (Child Class)。
  • 继承属性和方法: 子类自动拥有父类所有的属性和方法,就像“豪华公寓”自动拥有“标准公寓”的客厅、卧室和厨房一样。
  • 代码复用: 避免重复编写相同的代码,提高开发效率。

用代码来表达一下:

// 父类:标准公寓
class StandardApartment {
  constructor(rooms, bathrooms) {
    this.rooms = rooms;
    this.bathrooms = bathrooms;
    this.type = "Standard";
  }

  describe() {
    return `This is a ${this.type} apartment with ${this.rooms} rooms and ${this.bathrooms} bathrooms.`;
  }
}

// 子类:豪华公寓,继承自标准公寓
class LuxuryApartment extends StandardApartment {
  constructor(rooms, bathrooms, pool, theater, garden) {
    // 调用父类的构造函数,初始化 rooms 和 bathrooms
    super(rooms, bathrooms);
    this.pool = pool;
    this.theater = theater;
    this.garden = garden;
    this.type = "Luxury"; // 覆盖父类的 type 属性
  }

  describe() {
    // 调用父类的 describe() 方法,并添加额外的描述
    return super.describe() + ` It also has a pool, a private theater, and a rooftop garden! 🤩`;
  }
}

// 创建实例
const standardApartment = new StandardApartment(2, 1);
const luxuryApartment = new LuxuryApartment(3, 2, true, true, true);

console.log(standardApartment.describe()); // 输出:This is a Standard apartment with 2 rooms and 1 bathrooms.
console.log(luxuryApartment.describe()); // 输出:This is a Luxury apartment with 3 rooms and 2 bathrooms. It also has a pool, a private theater, and a rooftop garden! 🤩

在这个例子中,LuxuryApartment 使用 extends StandardApartment 声明了继承关系。它自动获得了 StandardApartmentroomsbathroomsdescribe() 方法。 同时,它又添加了自己独有的属性 pooltheatergarden,并 重写 (Override) 了 describe() 方法,让豪华公寓的描述更加完整。

是不是很神奇?就像魔术一样!🎩

第二幕:super 的秘密武器——父类的守护者

现在,问题来了:子类既然继承了父类的属性和方法,那么在子类的构造函数中,如何初始化那些从父类继承来的属性呢?

答案就是:super()

super() 的作用:

  • 调用父类的构造函数: 它就像一个电话,拨通了父类的构造函数,并把子类构造函数接收到的参数传递给父类,让父类负责初始化那些它拥有的属性。
  • 必须在子类构造函数的第一行调用: 这是一个非常重要的规则!如果你不小心忘记了,或者把它放在了其他位置,JavaScript 引擎会毫不留情地抛出一个错误。 就像你必须先穿好内裤才能穿裤子一样! 😅
  • 访问父类的方法: 除了调用构造函数,super 还可以让你访问父类中定义的方法。这在你需要扩展或修改父类方法的时候非常有用。

让我们再次回到“豪华公寓”的例子:

class LuxuryApartment extends StandardApartment {
  constructor(rooms, bathrooms, pool, theater, garden) {
    // 调用父类的构造函数,初始化 rooms 和 bathrooms
    super(rooms, bathrooms);  // 必须是第一行!
    this.pool = pool;
    this.theater = theater;
    this.garden = garden;
    this.type = "Luxury"; // 覆盖父类的 type 属性
  }

  describe() {
    // 调用父类的 describe() 方法,并添加额外的描述
    return super.describe() + ` It also has a pool, a private theater, and a rooftop garden! 🤩`;
  }
}

LuxuryApartment 的构造函数中,super(rooms, bathrooms) 负责调用 StandardApartment 的构造函数,并将 roomsbathrooms 传递给它,让 StandardApartment 来初始化这些属性。 而 this.pool = poolthis.theater = theaterthis.garden = garden 则负责初始化 LuxuryApartment 自己独有的属性。

describe() 方法中,super.describe() 调用了 StandardApartmentdescribe() 方法,获取了对标准公寓的描述,然后在此基础上添加了对豪华公寓额外功能的描述。

super 就像一个忠实的管家,默默地守护着父类的属性和方法,并确保它们能够正确地被子类使用。

第三幕:继承的进阶技巧——多重继承和方法重写

继承不仅仅是简单的复制和修改,它还有很多高级玩法。

1. 多重继承:

理论上,某些编程语言支持一个类同时继承多个父类。 就像一个人可以同时继承父母的基因一样。 但是,JavaScript 不支持真正的多重继承。

不过,我们可以通过一些技巧来模拟多重继承,比如:

  • 混入 (Mixins): 将多个类的功能 混入 到一个类中。
  • 组合 (Composition): 将多个类的实例 组合 到一个类中。

这些技巧比较复杂,我们以后有机会再详细讲解。

2. 方法重写 (Method Overriding):

子类可以重写父类的方法,提供自己的实现。 就像“豪华公寓”可以重写“标准公寓”的 describe() 方法,添加对游泳池、私人影院和屋顶花园的描述一样。

重写方法时,需要注意:

  • 方法签名必须相同: 方法名、参数列表和返回类型必须与父类的方法一致。
  • 可以使用 super 调用父类的方法: 这可以让你在父类方法的基础上进行扩展,而不是完全替换它。

第四幕:继承的注意事项——陷阱和最佳实践

继承虽好,但也要小心使用,避免掉入陷阱。

  • 过度继承: 不要为了继承而继承。 如果两个类之间的关系并不紧密,或者子类只需要父类的一小部分功能,那么使用继承可能不是一个好主意。 过度继承会导致代码结构复杂、难以维护。
  • 继承层次过深: 继承层次过深会导致代码难以理解和调试。 尽量保持继承层次的简洁。
  • 谨慎使用方法重写: 重写方法时,要确保子类的行为与父类一致,或者至少是兼容的。 否则,可能会导致意外的错误。

最佳实践:

  • 优先使用组合而不是继承: 组合是一种更灵活、更松耦合的设计模式。
  • 遵循 Liskov 替换原则: 子类应该能够替换父类,而不会导致程序的行为发生改变。
  • 使用接口 (Interfaces) 和抽象类 (Abstract Classes) 定义通用的行为: 这可以提高代码的可读性和可维护性。

第五幕:extendssuper 的本质——原型链的魔法

现在,让我们揭开 extendssuper 背后的秘密:原型链!

JavaScript 是一种基于原型的语言,每个对象都有一个原型对象。 当你访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到为止。

extendssuper 的本质,就是巧妙地利用原型链来实现继承。

  • extends 它将子类的原型对象指向父类,从而让子类继承父类的属性和方法。
  • super() 它实际上是调用了父类的构造函数,并设置了 this 的指向,让父类能够正确地初始化子类的属性。

可以用一张图来表示:

+---------------------+    [[Prototype]]    +---------------------+
|   Child Class       |--------------------->|   Parent Class      |
+---------------------+                       +---------------------+
|   constructor()     |                       |   constructor()     |
|   method1()         |                       |   method2()         |
+---------------------+                       +---------------------+
                                                 [[Prototype]]
                                                     |
                                                     V
                                           +---------------------+
                                           |   Object.prototype  |
                                           +---------------------+
                                           |   toString()        |
                                           |   valueOf()         |
                                           +---------------------+

理解了原型链,你就能更深刻地理解 extendssuper 的工作原理,并能够更加灵活地使用它们。

第六幕:总结与展望——继承的未来

今晚,我们一起探索了 extendssuper 的奥秘,从继承的诱惑,到 super 的秘密武器,再到继承的进阶技巧和注意事项,最后揭开了原型链的魔法。

extendssuper 是 JavaScript 中非常重要的语言特性,掌握它们能够让你写出更优雅、更高效、更易于维护的代码。

当然,继承并不是万能的。 在实际开发中,我们需要根据具体情况选择合适的设计模式,灵活运用继承、组合等技术,才能构建出健壮、可靠的应用程序。

希望今天的分享能够帮助你更好地理解 extendssuper,并在你的编程道路上助你一臂之力!

感谢大家的观看!我们下期再见!👋

表格总结:extends vs. super

特性 extends super
作用 建立父子类之间的继承关系,让子类自动继承父类的属性和方法。 调用父类的构造函数,初始化从父类继承来的属性。 访问父类的方法。
使用场景 定义一个类,并希望它继承另一个类的特性。 在子类的构造函数中,初始化从父类继承来的属性。 在子类的方法中,需要调用父类的方法,并在此基础上进行扩展。
语法 class ChildClass extends ParentClass { ... } super(arguments); (在构造函数中) super.methodName(arguments); (在方法中)
注意事项 只能继承一个类(单继承)。 必须在子类构造函数的第一行调用 super()
本质 通过修改原型链,让子类的原型对象指向父类。 访问父类的构造函数或方法。

希望这个表格能帮助你更好地记忆 extendssuper 的用法。 记住,理解了原理,才能更好地运用它们! 😉

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注