TypeScript 类

在 TypeScript 中,类是一种用于创建对象和组织代码的结构。

类的基本结构:

class MyClass {
    // 属性
    property1: type1;
    property2: type2;

    // 构造函数
    constructor(param1: type1, param2: type2) {
    	// this 当前的实例
        this.property1 = param1;
        this.property2 = param2;
    }

    // 方法
    method1(): returnType1 {
        // 方法体
    }

    method2(): returnType2 {
        // 方法体
    }
}

let my = new myClass();

结构说明

  • 属性:类可以包含多个属性,用于存储数据。属性可以有不同的类型,如字符串、数字、布尔值等。
  • 构造函数:构造函数是在创建类的实例时自动调用的特殊方法。它用于初始化类的属性。构造函数可以接受参数,以便在创建实例时传递初始值。
  • 方法:类可以包含多个方法,用于执行特定的操作。方法可以接受参数并返回结果。

关键字说明

  • class: 用于声明类。
  • constructor: 用于定义类的构造函数。
    • constructor被称为构造函数,构造函数会在创建实例时调用。new myClass();就是调用了constructor
  • new: 实例化class
  • this: 用于引用当前类的实例。

在引用任何一个类成员的时候都用了 this。 它表示我们访问的是类的成员。

继承

在 TypeScript 中,类继承是一种面向对象编程的特性,允许一个类(子类)从另一个类(父类)继承属性和方法。

使用 extends 关键字来实现继承。

示例:

class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

class Dog extends Animal {
    breed: string;
    constructor(name: string, breed: string) {
        super(name);
        this.breed = breed;
    }
    bark() {
        console.log('汪汪汪!');
    }
}

let dog = new Dog('Buddy', 'Labrador');
dog.move(10);  // Buddy moved 10m.
dog.bark(); // 汪汪汪!

console.log(dog instanceof Animal); // true

在这个例子中:

  • 定义父类 Animal ,它具有属性 name 、构造函数constructor和方法 move
  • 定义子类 Dog ,通过 extends 关键字继承自 Animal
    • Dog 必须调用 super(),它会执行基类的构造函数。
      在子类的构造函数中,必须先调用 super(),然后访问 this 的属性或方法。 这个是TypeScript强制执行的一条重要规则。

    • Dog 类有自己的属性 breed

    • Dog 类还具有自己的方法 bark

这个例子展示了最基本的继承:类从基类中继承了属性和方法。
Dog是一个 派生类,它派生自 Animal 基类,通过 extends关键字。 派生类通常被称作 子类,基类通常被称作 超类。

关键字说明

  • extends: 用于实现继承。
  • super: 用于调用父类构造函数或方法。
    • 如果一个类继承自另一个类,要在子类的构造函数中要访问 this 的属性或方法,必须先调用 super()。因为在子类的构造函数执行之前,父类的构造函数需要先被调用以完成父类部分的初始化工作。如果不先调用 super() 就访问 this 的属性,可能会导致错误,因为此时子类的实例还没有完全初始化。
  • instanceof: 用于检查对象是否是某个类的实例。

子类可以重写父类的方法,以提供自己的实现。
示例:

class Bird extends Animal {
    constructor(name: string) {
        super(name);
    }
    // 重写move方法
    move(distanceInMeters: number) {
        console.log(`${this.name} flew ${distanceInMeters}m.`);
    }
}

let bird = new Bird('Sparrow');
bird.move(5); 

let bird1: Animal = new Bird("magpie");
bird1.move(10);

示例中,Bird 类重写了父类 Animalmove 方法,以提供适合鸟类移动的描述。

注意:bird1 被声明为 Animal类型,但因为它的值是 Bird,调用 bird1.move(10)时,它会调用 Bird 里重写的move方法。

在不同的子类中重写父类的方法,使 方法 根据不同的类而具有不同的功能。

类继承有助于代码的复用和组织,通过继承,可以在子类中扩展和修改父类的功能,从而构建更复杂和有层次的对象模型。

类的访问修饰符

类支持访问修饰符:

  • public(公共):默认,可在任何地方访问。
  • private(私有):只能在类内部访问,子类和类的外部不能访问。
  • protected(受保护): 在类内部和子类中访问。

公共( public

public是默认的修饰符,如果没有明确指定,类成员就是公共的。
也可以明确的将一个成员标记成 public

public成员可以在类的内部、子类以及类的实例的任何地方被访问和修改。

重写上面的 Animal类:

class Animal {
    public name: string;
    public constructor(theName: string) { this.name = theName; }
    public move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

私有(private

当成员被标记成 private时,它就不能在声明它的类的外部访问。

private成员只能在其所属的类内部被访问和修改。在类的外部(包括子类)访问私有成员会导致编译错误。
private成员在类的实例中不能访问。

示例:

class Animal {
	// name 是 Animal 的私有成员
    private name: string;
    constructor(theName: string) { this.name = theName; }
}
class Dog extends Animal {
    constructor(name: string) {
        super(name);
    }
    bark() {
    	// Error:  Property 'name' is private and only accessible within class 'Animal'.
    	// 此句报错:属性“name”为私有属性,只能在类“Animal”中访问。
        console.log(`The dog's name is ${this.name}`);
    }
}

new Animal("Cat").name; // 错误: 属性“name”为私有属性,只能在类“Animal”中访问。

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

当比较带有 privateprotected成员的类型的时候:如果其中一个类型里包含一个 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");

// 合法,因为 Rhino 是 Animal 的子类,子类的对象可以赋值给父类类型的变量,这是多态的一种体现。
animal = rhino; 

// 错误。不能将类型“Employee”分配给类型“Animal”。类型具有私有属性“name”的单独声明。
// Employee 和 Animal 没有继承关系,并且它们的私有属性 name 是相互独立和不兼容的。
animal = employee; 

受保护(protected

protected修饰符与 private修饰符的行为很相似,但有一点不同: protected成员在派生类中仍然可以访问。
protected成员在类的实例中不能访问。

示例:

class Animal {
    protected name: string;
    constructor(theName: string) { this.name = theName; }
}
class Dog extends Animal {
	protected color: string;
    constructor(name: string, color: string) {
        super(name);
        this.color = color
    }
    bark() {
        console.log(`The dog's name is ${this.name}, and color is ${this.color}`);
    }
}

let dog = new Dog("Carl", "white");
dog.bark(); // "The dog's name is Carl, and color is white" 

new Animal("Cat").name; // 错误: 属性“name”受保护,只能在类“Animal”及其子类中访问。

注意:不能在 Animal 类外使用 name,可以通过 Dog 类的实例方法访问,因为 Dog 是由 Animal 派生而来的。

构造函数也可以被标记成 protected。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。
示例:

class Animal {
    protected name: string;
    // 把构造函数标记为protected
    protected constructor(theName: string) { this.name = theName; }
}
// Dog 能继承 Animal
// Dog 调用 `super()`,执行基类 Animal 受保护的 构造函数。
class Dog extends Animal {
	color: string;
    constructor(name: string, color: string) {
        super(name);
        this.color = color
    }
    bark() {
        console.log(`The dog's name is ${this.name}, and color is ${this.color}`);
    }
}

let dog = new Dog("Carl", "white");
dog.bark(); // "The dog's name is Carl, and color is white" 

new Animal("Cat").name; // 错误: 类“Animal”的构造函数是受保护的,仅可在类声明中访问。

readonly修饰符

readonly成员只能读取,不能被重新赋值。
readonly成员必须在声明时或构造函数里被初始化。

class Point {
  readonly x: number;
  readonly y: number = 10;
  constructor(x: number, y?: number) {
    this.x = x;
    this.y = y ?? this.y;
  }
  showPoint() {
  	console.log(`x: ${this.x}, y: ${this.y}`)
  }
}
let p = new Point(10, 20);
p.showPoint();

let p1 = new Point(50);
p1.showPoint();
p1.x = 30; // Error: 无法为“x”赋值,因为它是只读属性。

参数属性

在 TypeScript 中,类的参数属性是一种简洁的方式来同时声明和初始化类的属性。
示例:

class Point {
  constructor(readonly x: number, readonly y?: number) {}
}

在构造函数里仅使用 readonly x: number, readonly y?: number 参数来创建和初始化 xy
把声明和赋值合并在一个地方。

参数属性通过给构造函数参数前面添加一个访问限定符来声明。比如:

class Animal {
    constructor(protected name: string;) {}
}
class Animal {
    constructor(private name: string;) {}
}

getters/setters

TypeScript支持通过 getters(获取器)和 setters(设置器)来截取对对象成员的访问。

示例:没有使用getters/setters

class User {
    name: string;
}
let user = new User();
user.name = "Rigo";
console.log(user.name);  // "Rigo"
user.name = "张三";
console.log(user.name);  // "张三"

可以随意的设置 name

Animal 类改写成使用 getsetset方法中,验证用户输入的密码,密码正确才可以修改name。使用get 方法获取 name

class User {
  private _name: string = "";
  get name(): string {
    return this._name;
  }
  set name(newName: string) {
    let pwd = prompt("请输入密码", "password");
    if(pwd === "password") {
      this._name = newName;
    }else {
      console.log("密码错误!");
    }
  }
}

let user = new User();
user.name = "张三";
console.log(user.name); // 张三

User 类中,private _name: string声明变量会报错:Property ‘_name’ has no initializer and is not definitely assigned in the constructor.
这个错误是因为 TypeScript 在严格的类型检查模式下,要求非 readonly 且类型不可为 nullundefined 的属性必须在构造函数中被初始化,或者具有明确的初始值。

private _name: string = "";_name 属性添加初始值 "" 时,满足了 TypeScript 对于属性初始化的要求。TypeScript 就能够确定在对象创建时该属性有一个明确的初始状态,从而避免了可能出现未初始化就被使用的情况。

注意:只有 get 且没有 set 的存取器自动被推断为 readonly。 因为没有 set 提供修改值的方法。

静态属性

在 TypeScript 中,静态属性是属于类本身而不是类的实例的属性。

类的实例成员:仅当类被实例化的时候才会被初始化的属性。
类的静态成员:属于类本身而不是类的实例的属性。
每个实例通过 类名.静态属性 访问静态属性。比如示例中的MyClass.staticProperty
示例:

class MyClass {
    // 静态属性
    static staticProperty: number = 10;
    
    // 实例方法
    instanceMethod() {
        console.log(`实例化后访问静态属性值:${MyClass.staticProperty}`);
    }
}

// 直接通过类名访问静态属性
console.log(MyClass.staticProperty); 

// 实例化后,才能通过实例 instance 访问 instanceMethod()
let instance = new MyClass();
instance.instanceMethod();

let instance1 = new MyClass();
instance1.instanceMethod();

在这个示例中,无论创建多少个 instance 的实例,staticProperty 的值都是共享且不变的。

静态属性通常用于存储与类相关的全局数据或共享状态,并且在类的所有实例之间是共享的。

抽象类

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

// 定义抽象类 Animal
abstract class Animal {
	// 抽象方法 makeSound
    abstract makeSound(): void;
    // 非抽象方法 move
    move() {
        console.log('The animal is moving.');
    }
}

抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
抽象方法只定义方法签名。
抽象方法必须包含 abstract关键字并且可以包含访问修饰符。

abstract class Animal {
  abstract makeSound(): void; // 必须在派生类中实现
  move() {
      console.log("The animal is moving.");
  }
}

class Dog extends Animal {
  makeSound() {
      console.log("Woof!");
  }
  jump() {
    console.log("dog jump")
  }
}

let dog = new Dog();
dog.makeSound();
dog.move();
dog.jump();

let cat = new Animal(); // Error: 无法创建抽象类的实例。

由于 Animal 是抽象类,不能直接创建 Animal 的实例。

允许创建一个对抽象类型的引用:

abstract class Animal {
  abstract makeSound(): void; // 必须在派生类中实现
  move() {
      console.log("The animal is moving.");
  }
}

class Dog extends Animal {
  makeSound() {
      console.log("Woof!");
  }
}

class Cat extends Animal {
  makeSound() {
      console.log("喵~");
  }
}

// operateAnimal 函数接受一个类型为 Animal 的参数。
function operateAnimal(animal: Animal) {
    animal.makeSound();
    animal.move();
}

let dog = new Dog();
let cat = new Cat();
operateAnimal(dog);
operateAnimal(cat);

operateAnimal 函数接受一个类型为 Animal 的参数。
当我们调用 operateAnimal(dog)operateAnimal(cat) 时,参数传递的实际对象分别是 dogcat 的实例。在函数内部,我们可以通过 animal 这个抽象类型的引用统一地调用它们共有的方法。

把类当做接口使用

类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以能在允许使用接口的地方使用类。

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

构造函数

在 TypeScript 中,构造函数是类中的一个特殊方法,用于在创建类的实例时进行初始化操作。

构造函数的基本结构:

class MyClass {
    constructor(param1: type1, param2: type2) {
        // 初始化属性
        this.property1 = param1;
        this.property2 = param2;
    }
}

构造函数通常接受一些参数,这些参数可以用于初始化类的实例属性。在构造函数内部,可以使用 this 关键字来引用正在创建的实例,并为其属性赋值。

构造函数的作用

  • 初始化实例属性:构造函数的主要作用是在创建类的实例时,为实例的属性赋予初始值。这确保了每个实例都以特定的状态开始。
  • 执行其他初始化逻辑:除了初始化属性,构造函数还可以执行其他初始化逻辑,如调用其他方法、进行验证等。

示例:

class Person {
    name: string;
    age: number;

    // 这是构造函数
    constructor(name: string, age: number) {
    	console.log('构造函数执行了!')
        this.name = name;
        this.age = age;

        if(this.age <= 0) {
            throw Error('年龄不能小于等于0!');
        }
    }
}

let person = new Person('Alice', 25);
console.log('打印person:', person);

let person1 = new Person('John', -1); 

constructor被称为构造函数,构造函数会在创建实例时调用。
执行 new Person(...)就是在调用Person 类的constructor
在这里插入图片描述

构造函数与继承的关系

  • 子类构造函数中的 super ():当一个类继承自另一个类时,子类的构造函数必须先调用 super() 来调用父类的构造函数,以确保父类的初始化工作先完成。
  • 传递参数给父类构造函数:在子类构造函数中,可以通过 super() 传递参数给父类的构造函数,以满足父类的初始化需求。

示例:

class Animal {
    constructor(public name: string) {}
}

class Dog extends Animal {
    constructor(name: string, public breed: string) {
    	// 必须先调用super()
        super(name);
        // 然后才能访问this 的属性
        this.breed = breed;
    }
}

const dog = new Dog('Buddy', 'Labrador');
console.log(dog.name); // Buddy
console.log(dog.breed); // Labrador
  • 34
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值