TypeScript 中的类相比于 ES6 :

  • 增加了类型的校验
  • 增加了 public 等修饰符
  • 增加了抽象类

    基本用法

  1. class Person {
  2. name: string; // 校验
  3. sex: string;
  4. constructor(name: string, sex: string) { // 校验
  5. this.name = name; // 前面定义的属性必须进行初始化,在构造函数内初始化或者使用默认值,否则会报错
  6. this.sex = sex
  7. }
  8. }

属性默认值

class Person7 {
  name: string = 'aa';
  constructor(name: string) {
   // this.name = name // 设置默认值之后,可以不需要在这里初始化赋值
  }
}

可选属性

class Person7 {
  name?: string;
  constructor(name: string) {
   // this.name = name // 设置可选属性之后,可以不需要在这里初始化赋值
  }
}

静态属性

静态属性,指的是定义在类本身上面的属性或方法。可以通过类名直接访问这个属性。

class Person {
  static aa:string = 'woshishui';
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  saySomething() {
    console.log(Person13.aa) // 通过类名来访问这个静态属性
  }
}

继承

class Man extends Person {
  constructor(name: string, sex: string) {
    super(name,sex)
  }
}

:::info 派生类包含了一个构造函数,它必须调用 super(),它会执行基类的构造函数,需要传递相应的参数。 而且,在构造函数里访问 this 的属性之前,我们一定要调用 super()。 这个是TypeScript强制执行的一条重要规则。 :::

修饰符

TypeScript 增加了修饰符,使其更像 C++、Java 等语言一般。

修饰符有:

  • public 公有的
  • private 私有
  • protected 受保护的
  • readonly 只读的(这个是额外的)

public

public 是指公有的,所有的成员访问不受到任何限制,类中默认所有成员都是 public,所以一般可以不写。

class Person {
  public name: string;
  public sex: string;
  public constructor(name: string, sex: string) {
    this.name = name;
    this.sex = sex;
  }
  public eat() {
    console.log(this.name + 'is eating');
  }
}

private

private 修饰的属性不能在声明它的类外部访问,即不能在实例中直接访问,也不能在派生类中直接访问。只属于当前类,要在其他地方使用,只能通过调用当前类抛出的方法或者其他属性。

class Person {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
}

var person = new Person('aa')
person.name // 报错

// 继承类中也不能访问
class Man extends Person {
 constructor(name: string) {
   super(name)
 }
 eating() {
   // console.log(this.name) // 报错
 }
}

var man = new Man('bb')
man.name // 报错

:::warning TypeScript使用的是结构性类型系统。 当我们比较两种不同的类型时,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。

然而,当我们比较带有 private 或 protected 成员的类型的时候,情况就不同了。 如果其中一个类型里包含一个 private 成员,那么只有当另外一个类型中也存在这样一个 private 成员, 并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的。 对于 protected 成员也使用这个规则。 :::

class Animal {
  private name: string;
  constructor(theName: string) { this.name = theName; }
}

class Rhino extends Animal {
  constructor() { super("Rhino"); }
}

class Employee {
  private name: string;
  constructor(theName: string) { this.name = theName; }
}

let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");

animal = rhino;
animal = employee; // 错误: Animal 与 Employee 不兼容.

protected

protected 修饰的属性不能在当前类和派生类的实例中直接访问,但是在当前类和派生类内部可以直接访问。protected 是介于前两者之间的限制。

class Person {
  protected name: string;
  constructor(name: string) {
    this.name = name;
  }
}

var person = new Person('aa')
person.name // 报错,当前类实例不能直接访问

// 继承类中可以访问
class Man extends Person {
  constructor(name: string) {
    super(name)
  }
  eating() {
    console.log(this.name) // 成功
  }
}

var man = new Man('bb')
man.name // 报错,继承类实例不能直接访问

构造函数标记为 protected,当前类不能创建实例,其派生类可以继承,也能够创建实例。可以应用于不实例化的基类

class Person {
  name: string;
  protected constructor(name: string) {
    this.name = name;
  }
}

class Man extends Person {
  sex: string;
  constructor(name:string, sex:string) {
    super(name);
    this.sex = sex;
  }
}

var person = new Person('bb') // 报错
var man = new Man('bb', 'man')

readonly

readonly 修饰的属性是只读属性,初始化之后,再进行修改的时候,ts 会报错。readonly 修饰符可以和 public 等一起使用

class Person {
  readonly name: string;
  constructor(name: string) {
    this.name = name
  }
}

var person = new Person('xx')
person.name = 'asd' // 报错

参数属性

参数属性可以方便地让我们在一个地方定义并初始化一个成员。当一个成员带有修饰符时,可以直接将声明和初始化赋值合并到参数部分。此时参数名和属性名完全一致。

class Person {
  constructor(readonly name: string='aa') {
    // 直接合并声明和赋值的步骤到参数部分
  }
}
var person = new Person()
console.log(person.name) // 存在 name

参数属性通过给构造函数参数前面添加一个访问限定符来声明,可以是 public、private、protected、readonly

// 如果没有修饰符,是不能合并的
class Person {
  constructor(name: string='aa') {
  }
}
var person = new Person()
console.log(person.name) // 报错,不存在 name

抽象类

抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法。

abstract class Person {
  constructor(public name:string) {

  }
  work() {
    console.log('working');
  }
  abstract eat(): void
}

var person = new Person('aa') // 报错,不能实例化抽象类

class Man extends Person {
  eat() {
    console.log('eating')
  }
  play() {
    console.log('play')
  }
}

var man8 = new Man('bb') // 默认是 调用的构造函数 类型
console.log(man.name)
man.eat()
man.work()
man.play() // 正确

// 对比
var man:Person = new Man('bb') // 可以指定为抽象类的类型
console.log(man.name)
man.eat()
man.work()
man.play() // 报错,如果指定了 man 的类型为抽象类,play 没有在抽象类中定义,则不能使用

:::info 抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含 abstract 关键字并且可以包含访问修饰符。 :::

类与接口的关系

类实例接口

在定义类时,通过 implements 关键字带一个接口,用于约束类的实例的公共属性和方法,类必须严格实现接口中的属性和方法,可多不可少。并且接口只会约束公有成员,如果将一个接口中的属性实现为私有成员,那么也会报错。

interface DogInterface {
  eat(): void;
  feet: number;
}

class Dog3 implements DogInterface {
  eat() {
    console.log('eat');
  }
  feet = 2;
}
// 下面对 eat 方法进行私有化,会报错
class Dog3 implements DogInterface {
  private eat() {
    console.log('eat');
  }
  feet = 2;
}

类构造函数接口

类构造函数接口主要用于将类作为参数传递时,声明参数的类型。这个接口用于约束类的构造器以及静态属性和方法

interface DogConstructor {
  new(num: number): DogInterface;
  a: number;
  run(): void;
}
interface DogInterface {
  eat(): void;
  feet: number;
}

class Dog3 implements DogInterface {
  eat() {
    console.log('eat');
  }
  feet = 2;
  static a = 1;
  static run() {}
}

function createDog(ctor: DogConstructor, num: number): DogInterface {
  return new ctor(num);
}

let dog = createDog(Dog3, 1);

关系

截屏2020-06-04 22.54.32.png