这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战
this 与 super
下面,我们学习下如何使用 this 和 super。
首先在一个方法里面,是可以调用其他方法的。
在 catchMouse 方法里面,调用 sayHi 方法,我们也可以使用 super.sayHi 方法。
我们的猫相比于动物,它会多了一个属性,就是这个尾巴的长度,我们希望初始化的时候,除了传入 name 之外,还多传入一个长度的参数:length,来指代它的尾巴长度,我们就可以在 Cat 上添加一个 constructor 方法,来覆盖 Animal 的 constructor 方法,这个时候会有提示“派生类的构造函数必须包含'super' 调用"。
super 调用是什么呢?super 调用指代的就是 Animal 上的 constructor 的调用。
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi() {
console.log('Hi');
}
}
class Cat extends Animal {
tailLength: number;
constructor(name: string, length: number) {
super(name);
this.tailLength = length;
}
sayHi() {
console.log(`喵,我是 ${this.name},我的尾巴长度是 ${this.tailLength}`);
}
catchMouse() {
console.log('抓到了一只老鼠');
}
}
let a = new Cat('Tom', 10);
复制代码
习题:补全代码块,使结果为 `Ta is 10 years old
class Animal {
protected age: number;
constructor(age: number) {
this.age = age;
}
}
class Panda extends Animal {
private name: string;
constructor(name: string, age: number) {
// 在这补全代码
this.name = name;
}
public getPandaInfo(): string {
return `${this.name} is ${this.age} years old`;
}
}
const p = new Panda('Ta', 10);
p.getPandaInfo();
复制代码
答案:super(age);
解析:
const question = `${this.name} is ${this.age} years old`;
const answer = 'Ta is 10 years old';
复制代码
由结果可知 question
等价于 answer
。因此空白处操作是将 10
赋值为 age
。
子类的构造函数中需要包含父类的构造函数调用。使用关键字 super
在子类中访问和调用父类。
Panda
继承于父类 Animal
,父类 Animal
的构造函数为
this.age = age;
复制代码
恰好符合 age
的赋值操作,因此空白处应该填写 super(age)
。
资料:抽象类 vs 接口
我们知道,不同类之间公有的属性或方法,可以抽象成一个接口(Interfaces)。而抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现。
由于二者都描述了类的形状,因此我们在 TypeScript 中都可以使用它们来键入变量,所以可能会有同学困惑于二者的应用场景。
- 抽象类本质是一个无法被实例化的类,其中能够实现方法和初始化属性,而接口仅能够用于描述既不提供方法的实现,也不为属性进行初始化。
- 一个类可以继承一个类或抽象类,但可以实现(implements)多个接口
- 抽象类也可以实现接口
所以当我们仅需要用于描述类的形状而不需要统一抽象出来将之实现的时候,我们可以采用接口来进行描述。如果我们需要将通用的方法和属性提取出来统一实现和初始化的时候,我们应该采用抽象类进行提取。
假如我们要声明一个 Personal 类,具有 age 和 name 属性,同时拥有 say 和 sayHello 方法,同时 sayHello 方法不受其实例化影响永远返回 'hello',我们可以这样做
// 声明 IPerson 接口,仅声明但不实现
interface IPerson {
name: string;
age: number;
say(): void;
sayHello():string;
}
// 声明 AbstractPerson 抽象类,并实现 sayHello 方法
abstract class AbstractPerson implements IPerson {
age: number;
name: string;
abstract say(): void;
sayHello() {
return 'hello'
}
}
// 继承自 AbstractPerson 抽象类
class Person extends AbstractPerson {
age: number;
name: string;
say() {
return 'hi'
}
sayHello() {
return 'hello'
}
}
复制代码
资料:重写 vs 重载
重写和重载的概念对于初学者来说有时候会被混淆,其实二者是两个概念,这里再解读一下:
- 重写是指子类重写继承自父类中的方法
- 重载是指为同一个函数提供多个类型定义
我们通过一个例子来区分
class Animal {
say(word: string): string {
return word;
}
}
class Cat extends Animal {
// func1 重载,当输入的 text 是 number 类型时,将之除以 100 并返回保留小数点后两位的字符串
func1(text: number): number;
func1(text: any): any {
if (typeof text === 'number') {
return (text / 100).toFixed(2)
} else {
return text
}
}
// 重写了 Animal 的 say 方法
say(): string {
return 'Meow'
}
}
复制代码
资料:继承 vs 多态
继承(Inheritance)
子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
多态(Polymorphism)
由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal,但是分别实现了自己的 sayHi 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 sayHi 方法,程序会自动判断出来应该如何执行 sayHi。
// 父类 Animal
class Animal {
sayHi(name: string): string {
return `My name is` + name;
}
}
// Cat 继承自 Animal
class Cat extends Animal {
sayHi(name: string): string {
return 'Meow, MyName is ' + name;
}
// Cat 实现了自己的 eat 方法
eat(){
console.log('eating fish')
}
}
// Dog 继承自 Animal
class Dog extends Animal {
sayHi(name: string): string {
return 'Wang, MyName is ' + name;
}
}
// 多态,声明 anAnimal 为 Animal 类型,当 anAnimal 被 new 为不同的子类时,他们的 sayHi 方法返回了不同的内容
let anAnimal: Animal;
anAnimal = new Cat();
const tomSayHi = anAnimal.sayHi('Tom'); // Meow, MyName is Tom
anAnimal = new Dog();
const spikeSayHi = anAnimal.sayHi('Spike'); // Wang, MyName is Jeff
复制代码