likes
comments
collection
share

TS系列篇|类(class)

作者站长头像
站长
· 阅读数 70

"不畏惧,不将就,未来的日子好好努力"——大家好!我是小芝麻😄

类(Class)定义了一件事物的抽象特点,包含它的属性和方法

1、定义类

TypeScript 中,我们也是通过 Class 关键字来定义一个类, 使用 constructor 定义构造函数。

构造函数: constructor

  • 主要用于初始化类的成员变量属性
  • 类的对象创建时自动调用执行
  • 没有返回值
class Animal {
    public name: string;
    constructor(name: string) {
        this.name = name;
    }
    sayHi(): string {
        return `My name is ${this.name}`;
    }
}

2、类的继承

使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。

  • 子类继承父类后子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性
  • 将子类共用的方法抽象出来放在父类中,自己特殊逻辑放在子类中重写父类的逻辑
  • super 可以调用父类上的方法和属性: (相当于ES5的:在静态方法和构造函数中指向父类; 在普通函数中指向父类的prototype;)
class Animal {
    public name: string;
    constructor(name: string) {
        this.name = name;
    }
    sayHi(): string {
        return `My name is ${this.name}`;
    }
}

class Cat extends Animal {
  constructor(name) {
    super(name); // 调用父类的 constructor(name)
    console.log(this.name);
  }
  sayHi(): string { // 将子类共用的方法抽象出来放在父类中,自己特殊逻辑放在子类中重写父类的逻辑
    return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
  }
}

let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom

2.1 重写(override) VS 重载(overload)

  • 重写是指子类重写继承自父类的方法
  • 重载是指为同一个函数提供多个类型定义

2.2 继承 VS 多态

  • 继承(inheritance)子类继承父类,子类除了拥有父类的所有特性外,还有一些具体的特性
  • 多态(Polymorphism)由继承而产生了相关的不同的类,对同一个方法可以有不同的行为

3、类的修饰符

TypeScript 中有三类访问修饰符,分别是: publicprivateprotected不写默认为 public

  • public :自己、自己的子类 和其他类都可以访问 (默认值)
  • protected 受保护的 自己和自己的子类能访问, 其他类不能访问
  • private 私有的 只能自己访问,自己的子类不能访问,其他类更不能访问
class Father {
  public name: string
  protected age: number
  private money: number

  constructor(name: string, age: number, money: number) {
    this.name = name
    this.age = age
    this.money = money
  }
  getName(): string {
    return this.name
  }
  setName(name: string): void {
    this.name = name
  }
}
class child extends Father {
  constructor(name: string, age: number, money: number) {
    super(name, age, money)
  }
  desc() {
    console.log(`${this.name}${this.age}${this.money}`)
    // 属性“money”为私有属性,只能在类“Father”中访问
  }
}
let children = new child('金色小芝麻', 18, 1000)
console.log(children.name)
console.log(children.age) // ERROR 属性“age”受保护,只能在类“Father”及其子类中访问。
console.log(children.money) // ERROR 属性“money”为私有属性,只能在类“Father”中访问。
  • 当构造函数修饰为 private 时,该类不允许被继承或者实例化:
class Animal {
  public name: string;
  private constructor(name: string) {
    this.name = name;
  }
}
class Cat extends Animal { // 无法扩展类“Animal”。类构造函数标记为私有。
  constructor(name: string) {
    super(name);
  }
}

let a = new Animal('Jack'); // 类“Animal”的构造函数是私有的,仅可在类声明中访问。
  • 当构造函数修饰为 protected 时,该类只允许被继承:
class Animal {
  public name: string;
  protected constructor(name: string) {
    this.name = name;
  }
}
class Cat extends Animal {
  constructor(name: string) {
    super(name);
  }
}

let a = new Animal('Jack'); // 类“Animal”的构造函数是受保护的,仅可在类声明中访问。

4、readonly

readonly 修饰的变量只能在属性声明时或 构造函数 中初始化

class Animal {
  // 如果只读修饰符和可见性修饰符同时出现,需要将只读修饰符写在可见修饰符后面。
  public readonly name: string
  constructor(name: string) {
    this.name = name
  }
  changeName(name: string) {
    this.name = name // ERROR 无法分配到 "name" ,因为它是只读属性。
  }
}
let a = new Animal('hello')
a.name = '111' // ERROR 无法分配到 "name" ,因为它是只读属性
a.changeName('nihao') // ERROR 无法分配到 "name" ,因为它是只读属性
console.log(a)

readonly 只是在 编译阶段进行代码检查。运行时依然能打印

  • 编译时: TS系列篇|类(class)
  • 运行时: TS系列篇|类(class)

5、参数属性

在上面的例子中,都是在类的定义的顶部初始化实例属性,在 constructor 里接收参数然后对实例属性进行赋值,参数属性就是为了简化这一过程的

直接在 constructor 构造函数的参数前面加上修饰符或readonly => 等同于在类中定义该属性同时给该属性赋值,使代码更简洁。

class User {
  constructor(public name: string) {}
}
let user = new User('hello')
console.log(user.name) // hello
user.name = 'nihao'
console.log(user.name) // nihao

6、存取器

  • TypeScript 中,我们可以通过 getter/ setter来改变一个类中属性的读取和赋值行为
class Person {
  name: string
  constructor(name: string) {
    this.name = name
  }
  get getName() { // 读取
    return this.name
  }
  set setName(val: string) { // 赋值
    this.name = val.toUpperCase()
  }
}
let p1 = new Person('nihao')
console.log(p1.getName)
p1.setName = 'hello'
console.log(p1.name)

7、静态属性和静态方法static

使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:

class Father {
  public name: string
  constructor(name: string) {
    this.name = name
  }
  public static className: string = 'Father'
  static getClassName(): string {
    return Father.className
  }
  
}
console.log(Father.className)
console.log(Father.getClassName())

8、抽象类和抽象方法abstract

使用 abstract 关键字来定义抽象类和在抽象类内部定义抽象方法。

8.1 抽象类

  • 抽象描述一种抽象的概念,做为其它类的基类使用
  • 无法创建抽象类的实例,抽象类只能被继承
abstract class Animal {
  name!: string
  abstract speak(): void
}

class Cat extends Animal {
  speak() {
    console.log('喵喵喵')
  }
}

let animal = new Animal(); // ERROR 无法创建抽象类的实例
let cat = new Cat()
cat.speak() // 喵喵喵

8.2 抽象方法

  • 抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现
  • 抽象方法只能出现在抽象类中
abstract class Animal {
  name: string
  abstract speak(): void = ()=>{} // ERROR 方法“speak”不能具有实现,因为它标记为抽象。
}
class Cat extends Animal {
  speak() { // 在这里实现 speak 方法
    console.log('喵喵喵')
  }
}
  • 子类可以对抽象类进行不同的实现
abstract class Animal {
  abstract speak(): void
}
class Dog extends Animal {
  speak() {
    console.log('汪汪汪')
  }
}

class Cat extends Animal {
  speak() {
    console.log('喵喵喵')
  }
}

8.3 抽象类 VS 接口

  • 不同类之间公有的属性或方法,可以抽象成一个接口(interfaces), 而抽象类是供其他类继承的基类
  • 抽象类本质是一个无法被实例化的类,可以包含成员的实现细节,而接口仅能够用于描述,既不能提供方法的实现细节,也不为属性进行初始化
  • 一个类可以继承一个类或抽象类,但可以实现(implements)多个接口
  • 抽象类也可以实现接口
abstract class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  abstract speak(): void;
}
interface Flying {
  fly(): void
}
interface age {
  age: number
}
class Dog extends Animal implements Flying, age {
  age: 18
  speak() {
    console.log('汪汪汪')
  }
  fly() {
    console.log('我会飞')
  }
}
访问控制修饰符private、protected、public
只读属性readonly
静态属性static
抽象类、抽象方法abstract

参考文献

[1]. TypeScript中文网

[2]. TypeScript 入门教程

转载自:https://juejin.cn/post/7006482612306837534
评论
请登录