TypeScript进阶知识之类(类的定义、类的基本使用、类的构造函数、类的属性和方法、访问修饰符、类的继承、抽象类)

系列文章目录

引入一:Typescript基础引入(基础类型、元组、枚举)
引入二:Typescript面向对象引入(接口、类、多态、重写、抽象类、访问修饰符)
第一章:Typescript基础知识(Typescript介绍、搭建TypeScript环境、基本数据类型)
第二章:Typescript常用类型(任意值any、数组Array、函数Function、元组Tuple、类型推论、联合类型)
第三章:Typescript基础知识(类型断言、类型别名、字符串字面量类型、枚举、交叉类型)
第四章:Typescript基础知识(类型拓宽、类型缩小)
第五章:TypeScript进阶知识之类(类的定义、类的基本使用、类的构造函数、类的属性和方法、访问修饰符、类的继承、抽象类)
第六章:TypeScript进阶知识之接口(接口定义、接口属性、可索引类型、接口表示函数类型、额外的属性检查、接口继承、接口与类型别名的区别)
第七章:TypeScript进阶知识之泛型(泛型的定义、为什么要使用泛型、泛型的使用、泛型变量、多个类型参数、泛型类、泛型接口、泛型参数默认类型、泛型约束)


文章目录

  • 系列文章目录
  • 一、类的定义
  • 二、类的基本使用
  • 三、类的构造函数
    • 3.1 构造函数的基本语法
    • 3.2 构造函数初始化成员变量
    • 3.3 创建实例并调用构造函数
    • 3.4 构造函数可选参数和默认值
    • 3.5 调用其他构造函数(构造函数重载)
  • 四、类的属性和方法
    • 4.1 实例属性和实例方法
    • 4.2 静态属性和静态方法
  • 五、类的继承
  • 六、访问修饰符
    • 6.1 public
    • 6.2 private
    • 6.3 protected
    • 6.6 readonly
    • 6.7 参数属性
  • 七、抽象类

一、类的定义

在 TypeScript 中,可以使用 class 关键字来定义类。类的定义通常包括成员变量、构造函数、方法等。

二、类的基本使用

类的基本使用主要有以下几个步骤:

  • 定义类及成员变量: 使用 class 关键字定义一个类,并在类中声明成员变量。
  • 构造函数: 使用 constructor 方法定义构造函数,用于在创建类的实例时初始化对象的属性。
  • 方法:类中定义方法,可通过类的实例调用
  • 创建类的实例: 使用 new关键字创建类的实例,并传递构造函数所需的参数。
  • 访问成员变量和调用方法: 通过实例对象访问成员变量和调用方法
class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  sayHello(): void {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

const p = new Person("Echo", 26);
console.log(p.name); // 输出:Echo
console.log(p.age);  // 输出:26
p.sayHello();        // 输出:Hello, my name is Echo and I'm 26 years old.

三、类的构造函数

TypeScript 类中,构造函数用于在创建类的实例时进行初始化操作。构造函数使用 constructor 关键字来定义,可以接收参数,并在创建对象时调用。

3.1 构造函数的基本语法

class ClassName {
  constructor(parameter1: Type1, parameter2: Type2, ...) {
    // 书写构造函数的逻辑
  }
}

3.2 构造函数初始化成员变量

构造函数可以用来初始化类中的成员变量,通过接收构造函数的参数,并将其赋给对应的成员变量。成员变量的声明通常放在类的顶部,而初始化则在构造函数中进行。

class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

在上述示例中,构造函数接收 nameage 作为参数,并将参数的值分别赋给类中的 nameage 成员变量。

3.3 创建实例并调用构造函数

使用 new 关键字创建类的实例时,构造函数会被自动调用,让我们可以在创建实例的同时进行初始化操作。

const person = new Person('Echo', 26);

在上述代码中,我们创建了一个 Person 类的实例 person,并传递了 ‘Echo’ 和 26 作为构造函数的参数。构造函数会将这些参数的值分别赋给 person 实例的 name 和age 成员变量

3.4 构造函数可选参数和默认值

构造函数的参数可以设置为可选的,并且可以为参数提供默认值

可选参数使用问号( ? )修饰符进行标记,而默认值则使用等号(=)进行赋值。

class Person {
  name: string;
  age: number;

  constructor(name: string = 'Echo', age?: number) {
    this.name = name;
    this.age = age;
  }
}

// 创建 person 实例,但不传递 name 和 age 参数
const person = new Person();
console.log(person.name); // 输出:Echo
console.log(person.age);  // 输出:undefined

// 创建 person1 实例,只传递 name 参数
const person1 = new Person('Jee');
console.log(person1.name); // 输出:Jee
console.log(person1.age);  // 输出:undefined

// 创建 person2 实例,同时传递 name 和 age 参数
const person2 = new Person('James', 35);
console.log(person2.name); // 输出:James
console.log(person2.age);  // 输出:35

在上述示例中,name 参数具有一个默认值 'Echo',而 age 参数则是可选的。如果在创建实例时不传 nameage 参数,那么 name 会输出默认值 'Echo',而 age 会被设置为 undefined,如果在创建实例时只传递了 name 参数,而没有传递 age 参数,那么 age 也会被设置为 undefined

3.5 调用其他构造函数(构造函数重载)

在一个类中,可以定义多个构造函数,并通过不同的参数配置来进行重载。重载的构造函数之间可以相互调用,使用 this 关键字来引用当前类的实例。
构造函数重载需要定义多个具有不同参数类型和数量的构造函数签名。构造函数签名是指构造函数名称和参数列表,通过这些不同的签名来区分不同的构造函数。

class ClassName {
  constructor(parameter1: Type1);
  constructor(parameter1: Type1, parameter2: Type2);
  constructor(parameter1: Type1, parameter2: Type2, parameter3: Type3);
  // ...
  constructor(parameter1: Type1, parameter2: Type2, parameter3: Type3, ...) {
    // 书写构造函数实现的逻辑
  }
}

上面的示例中,我们定义了三个构造函数签名,每个签名有不同的参数类型和数量,以提供不同的构造函数选项。

class Person {
  name: string;
  age: number;

  constructor(name: string);
  constructor(name: string, age: number);
  constructor(name: string, age?: number) {
    this.name = name;
    if (age) {
      this.age = age;
    } else {
      this.age = 0;
    }
  }
}

const person1 = new Person('Echo');
const person2 = new Person('Echo', 26);

console.log(person1.name, person1.age); // 输出:Echo 0
console.log(person2.name, person2.age); // 输出:Echo 26

在上述示例中,我们定义了两个构造函数签名,第一个构造函数接收一个 name 参数,第二个构造函数接收一个 name 和一个 age 参数。在构造函数的实现中,根据传递的参数情况,决定是否给 age 成员变量赋值。接着,我们创建了两个实例 person1person2,第一次实例化传递了一个 name 参数,调用了第一个构造函数。第二次实例化传递了一个 name 参数和一个 age 参数,调用了第二个构造函数。

注意:

  • 成员初始化(比如 name: string)后,才可以通过 this.name 来访问实例成员
  • 需要为构造函数指定类型注解,否则会被隐式推断为 any 类型,构造函数不需要返回值类型。

四、类的属性和方法

4.1 实例属性和实例方法

实例方法定义在类中的成员方法,用于操作和访问类的实例属性,实例方法可以通过使用 this 关键字直接访问类的实例属性,并执行特定的操作。实例方法和属性可以通过类的实例来调用,用于对特定实例进行特定操作。

class Person {
  name: string;
  age: number;

	constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  sayHello(): void {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

const person = new Person("Echo", 26);
person.sayHello(); // 输出:Hello, my name is Echo and I'm 26 years old.

4.2 静态属性和静态方法

static 修饰符定义的属性和方法,不可以通过 this 取访问,只能通过类名去调用,不需要实例化。

class Person {
  static id = '123';
  constructor() {
    this.say(); // Error: 属性“say”在类型“Person”上不存在。你的意思是改为访问静态成员“Person.say”吗?
  }
  static say(slogan): void {
    console.log(slogan);
    console.log('id', Person.id); // id 123
  }
}
Person.say('泰裤辣'); // 泰裤辣
console.log('Person.id', Person.id); // Person.id 123

五、类的继承

当一个类继承另一个类时,它会**继承父类的属性和方法**,并可以通过重载或添加新的属性和方法来扩展父类。继承使用 extends 关键字来建立类之间的关系。

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

class Animal {
  name:string;
  constructor(name:string) {
      this.name = name;
  }
  sayHi() {
      return `My name is ${this.name}`;
  }
}

class Cat extends Animal {
constructor(name:string) {
  super(name); // 调用父类的 constructor(name)
  console.log(this.name);
}
// 如果子类中方法签名与父类一致 则会对父类方法进行重写
sayHi() {
  return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
}
}

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

let d:Cat=new Animal ('Dog');
console.log(d.sayHi());//My name is Dog
  • 子类继承了父类,父类类型得引用指向子类对象,实例无法调用子类对象特性(属性、方法),但是子类可以可以调用父类对象特性,还可以通过父类进行new实例,一种对象多种形态。
  • 如果子类方法签名与父类一致,子类会对父类得方法进行重写.

六、访问修饰符

类的修饰符用于控制类的成员(属性和方法)的可见性和访问权限。

类的修饰符包括:

  • public(公有的),可以在 任何地方被访问到,默认所有的属性和方法都是 public 的。

  • private(私有的)不能在声明它的类的外部访问

  • protected(受保护的),和 private 类似,区别是 它在子类中也是允许被访问的

  • readonly(只读的)属性为只读的

6.1 public

public 关键字是默认的访问修饰符,如果不指定修饰符,默认为 public。公共成员在类的内部和外部都是可见的,并且可以随时访问。

class Person {
  public name: string;
  public age: number;

  public constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  public sayHello(): void {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

const person = new Person("Echo", 26);
console.log(person.name); // 输出:ECho
person.sayHello();        // 输出:Hello, my name is Echo and I'm 26 years old.
person.name = "James";
console.log(person.name); // 输出:James

6.2 private

private 关键字修饰符限制成员的访问范围仅在类的内部。私有成员在类的外部不可见,只能在类的内部进行访问。

class Person {
  private name: string;

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

  public sayHello(): void {
    console.log(`Hello, my name is ${this.name}.`);
  }
}

const person = new Person("Echo");
person.sayHello();        // 输出:Hello, my name is Echo.
console.log(person.name); // 报错:属性“name”为私有属性,只能在类“Person”中访问
  • 注意: 使用 private 修饰的属性或方法,在子类中也是不允许访问的。

    class Animal {
      private name: string;
      public constructor(name: string) {
        this.name = name;
      }
    }
    
    class Dog extends Animal {
      constructor(name: string) {
        super(name);
        console.log(this.name); // 报错:属性“name”为私有属性,只能在类“Animal”中访问
      }
    }
    
  • 注意: 当构造函数修饰为 private 时,该类不允许被继承或者实例化

    class Animal {
      public name: string;
      private constructor(name: string) {
        this.name = name;
      }
    }
    class Dog extends Animal { // 报错:无法扩展类“Animal”,类构造函数标记为私有
      constructor(name: string) {
        super(name);
      }
    }
    
    const dog = new Animal('Hate'); // 报错:类“Animal”的构造函数是私有的,仅可在类声明中访问。
    

6.3 protected

protected 关键字修饰符限制成员的访问范围在类的内部及其派生类中。受保护成员在类的外部不可见,但可以在类的内部和派生类中进行访问。

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

class Dog extends Animal {
  constructor(name: string) {
    super(name);
    console.log(this.name); 
  }
}

const dog = new Dog('Hate');// 输出:Hate
  • 注意: 当构造函数修饰为 protected 时,该类只允许被继承。

    class Animal {
      public name: string;
      protected constructor(name: string) {
        this.name = name;
      }
    }
    class Dog extends Animal {
      constructor(name: string) {
        super(name);
      }
    }
    

6.6 readonly

readonly 是一个只读属性关键字,只允许出现在属性声明或索引签名或构造函数中

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

let person = new Person('Echo');
console.log(person.name); // 输出:Echo
person.name = 'James';    // 报错:无法为“name”赋值,因为它是只读属性

6.7 参数属性

参数属性是一种简化代码的语法糖,用于 在构造函数中同时声明和初始化类的成员属性。使用参数属性可以在一个地方完成属性的声明和赋值,减少了重复的代码

class Person {
  constructor(public name: string, private age: number, protected sex: string, public readonly height: number) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.height = height;
  }
}

const person = new Person('Echo', 26, 'male', 1.7);
console.log(person.name);   // 输出:Echo
console.log(person.age);    // 报错:属性“age”为私有属性,只能在类“Person”中访问
console.log(person.sex);    // 报错:属性“sex”受保护,只能在类“Person”及其子类中访问
console.log(person.height); // 输出:1.7

七、抽象类

abstract 用于定义抽象类和其中的抽象方法。

什么是抽象类? ​
一般情况下,抽象类无法创建对象,抽象类中可以存在抽象方法,没有方法体得方法就是抽象方法,抽象类中可以存在抽象方法和非抽象方法,一个类中具有抽象方法,那个该类一定是抽象类

特点:

  • 抽象类不能被实例化,只能被继承。
  • 抽象类可以包含抽象方法和具体方法的定义。
  • 子类必须实现抽象类中的所有抽象方法,否则子类也必须声明为抽象类。
  • 如果一个类继承了一个抽象类,那么它必须实现抽象类中的抽象方法,除非它自身也声明为抽象类。
  • 抽象类可以作为其他类的基类,用于提供共享的属性和方法定义。
abstract class Person1 {
    abstract study(): void;
}

// let s1 = new Student1();

// 子类继承抽象类 两种方式:
// 1.实现抽象类中方法
class Student1 extends Person1 {
    study(): void {
        console.log("好好学习")
    }
    cook(): void {
        console.log("学会了做饭")
    }
}
let s3: Student1 = new Student1()

s3.study();//好好学习
s3.cook();//学会了做饭

// 2.自身也成为抽象类
// abstract class Student2 extends Person1 {
//     // study(): void;
// }

你可能感兴趣的:(#,ts,typescript,前端,javascript)