ts中的类

在 TypeScript 中,类是一种基于面向对象编程思想的数据结构,它允许你定义对象的蓝图,包含属性和方法。类可以用于创建对象的实例,并且可以继承其他类。

typescript 复制代码
class Animal {
  // 属性
  name: string;

  // 构造函数
  constructor(name: string) {
    this.name = name;
  }

  // 方法
  move(distance: number = 0) {
    console.log(`${this.name} moved ${distance} meters.`);
  }
}
  • name: string;: 定义了一个属性 name,它的类型是字符串。
  • constructor(name: string): 构造函数,用于在创建类的实例时初始化对象。这里接受一个参数 name,并将其赋值给类的属性 name。
  • move(distance: number = 0): 定义了一个方法 move,它接受一个参数 distance,默认值为 0。在这个例子中,方法简单地打印一条消息。

可以使用这个类创建对象的实例

arduino 复制代码
const dog = new Animal("Dog");
dog.move(10); // 输出: Dog moved 10 meters.

修饰符

public

在 TypeScript 中,public 是一个访问修饰符,用于指定类的属性或方法是公共的,即可以在类的内部和外部访问。如果你不显式地使用访问修饰符,默认情况下属性和方法都是公共的。

下面是一个使用 public 的例子:

typescript 复制代码
class Animal {
  public name: string;

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

  public move(distance: number = 0) {
    console.log(`${this.name} moved ${distance} meters.`);
  }
}

const dog = new Animal("Dog");
console.log(dog.name); // 可以访问公共属性
dog.move(10); // 可以调用公共方法

在这个例子中,name 属性和 move 方法都被标记为 public,因此它们可以在类的内部和外部访问。

有时候,显式地写上 public 可以提高代码的可读性,特别是当你使用 TSLint 等工具时,它们可能会强制执行使用访问修饰符,以确保开发者清晰地知道属性和方法的可见性。

需要注意的是,即使不显式地写上 public,默认情况下属性和方法也是公共的。在很多情况下,直接使用默认的公共访问级别是合理的。

private

在 TypeScript 中,private 是一个访问修饰符,用于指定类的属性或方法是私有的。私有成员在类的外部是不可访问的,只有在类的内部才能够使用。使用 private 可以有效地封装类的内部实现细节,防止外部直接访问和修改敏感数据。私有成员只能在定义它们的类中访问,这有助于提高代码的安全性和可维护性。

对于下列例子,定义了一个 Parent 类,其中包含一个私有属性 age,并在构造函数中初始化它。然后我们创建了一个 Parent 类的实例 p,并尝试访问该实例的 age 属性。

typescript 复制代码
class Parent {
  private age: number;
  
  constructor(age: number) {
    this.age = age;
  }
}

const p = new Parent(18);
console.log(p);      // { age: 18 }
console.log(p.age);   // error,属性"age"为私有属性,只能在类"Parent"中访问
console.log(Parent.age); // error,类型"typeof ParentA"上不存在属性"age"

在这里,可以看到实例 p 的打印结果包含 age 属性,但在尝试直接访问 p.age 时,TypeScript 编译器会报错,因为 age 是一个私有属性,只能在类 Parent 中访问。

接下来,定义了一个名为 Child 的类,它继承自 Parent。在 Child 类的构造函数中,我们使用 super(age) 调用了父类的构造函数,并尝试在构造函数中通过 super.age 访问父类的私有属性。

super(age) 的 age 是父类 Parent 的构造函数的参数。这里的目的是将传递给子类构造函数的 age 参数传递给父类构造函数,以便在创建子类实例时完成对父类属性的初始化。这样可以确保父类的构造函数正确地处理其所需的参数。

scala 复制代码
class Child extends Parent {
  constructor(age: number) {
    super(age);
    console.log(super.age); // error,通过 "super" 关键字只能访问基类的公共方法和受保护方法
  }
}

在这里,super.age 会导致编译错误,因为使用 super 关键字只能访问基类的公共方法和受保护方法,而私有属性是无法在子类中直接访问的。私有成员是在声明它们的类内部可见的,子类无法直接访问父类的私有成员。

protected

protected 修饰符用于声明类的成员(属性或方法),表明这些成员在其所属的类及其子类中可访问,但在类的实例外部是不可访问的。

typescript 复制代码
class Parent {
  protected age: number;

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

  protected greet(): void {
    console.log(`Hello, I'm ${this.age} years old.`);
  }
}

class Child extends Parent {
  constructor(age: number) {
    super(age);
  }

  public introduce(): void {
    this.greet(); // 在子类中可以访问父类的 protected 方法
    console.log("I'm a child.");
  }
}

const parent = new Parent(35);
// parent.age;    // Error: 属性 'age' 受保护,只能在类 'Parent' 及其子类中访问
// parent.greet(); // Error: 方法 'greet' 受保护,只能在类 'Parent' 及其子类中访问

const child = new Child(10);
// child.age;     // Error: 属性 'age' 受保护,只能在类 'Parent' 及其子类中访问
// child.greet();  // Error: 方法 'greet' 受保护,只能在类 'Parent' 及其子类中访问
child.introduce(); // 在子类的公共方法中可以访问父类的 protected 方法

Parent 类包含了一个 protected 的 age 属性和一个 protected 的 greet 方法。子类 Child 继承了 Parent,并在自己的 introduce 方法中调用了 this.greet()。

在这个例子中,greet 方法被定义为 protected,这意味着它只能在 Parent 类内部或其子类中访问。所以,在 Child 类中的 introduce 方法可以调用 this.greet(),因为 Child 是 Parent 的子类

然而,外部创建的 Parent 实例 parent 不能直接访问 age 属性或调用 greet 方法,因为它们都是受保护的。同样,外部创建的 Child 实例 child 也不能直接访问 age 属性或调用 greet 方法。但是,它可以调用 introduce 方法,而在 introduce 方法中,它可以通过 this.greet() 访问 greet 方法。

总结一下:

  • protected 成员(属性或方法)可以在定义它们的类内部和它们的子类中访问。
  • 外部创建的类实例不能直接访问受保护的成员。
  • 子类可以访问父类中受保护的成员,包括调用受保护的方法。

在 TypeScript 中,protected 还可以用来修饰构造函数,该构造函数加上 protected 修饰符之后,就不能直接用来创建实例了。这样的构造函数通常被设计成基类,只能被其子类继承,而不允许在外部直接实例化。

csharp 复制代码
class Base {
  protected constructor() {
    // 受保护的构造函数逻辑
  }

  protected greet(): void {
    console.log("Hello from the base class!");
  }
}

在上面的例子中,Base 类的构造函数被标记为 protected。这意味着我们不能直接通过 new Base() 来创建 Base 类的实例。如果我们尝试这样做,TypeScript 将会报错。

然而,子类仍然可以继承这个受保护的构造函数和其他受保护的成员。

scala 复制代码
class Derived extends Base {
  constructor() {
    super(); // 可以在子类中调用基类的受保护构造函数
  }

  public doSomething(): void {
    this.greet(); // 可以在子类中访问基类的受保护方法
  }
}

readonly修饰

在 TypeScript 中,readonly 是一个关键字,用于声明只读属性。当一个属性被标记为 readonly 后,它只能在声明时或构造函数中被赋值,之后就不能再修改了。这样可以确保该属性在实例化之后保持不变。

UserInfo 类有一个只读属性 name

typescript 复制代码
class UserInfo {
  readonly name: string;

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

name 被标记为 readonly,所以它只能在构造函数中进行赋值。一旦实例化完成,就不能再修改 name 属性的值。

ini 复制代码
const user = new UserInfo("Lison");
user.name; // 可以读取属性值,输出 "Lison"
user.name = "haha"; // 编译错误,不能对只读属性进行赋值

尝试修改 name 属性的值将导致编译错误,因为 name 是只读的,不允许在实例化之后被修改

参数属性

在 TypeScript 中,参数属性是一种简化类定义和初始化成员变量的方法。它允许你在构造函数的参数前面加上访问修饰符(如 public、private、protected、readonly),并在构造函数中直接使用这个参数,从而省略了在类中显示初始化的步骤

比较一下使用参数属性和不使用参数属性的例子:

typescript 复制代码
// 不使用参数属性
class A {
  name: string;

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

const a = new A("aaa");
console.log(a.name); // "aaa"

// 使用参数属性
class B {
  constructor(public name: string) {}
}

const b = new B("bbb");
console.log(b.name); // "bbb"

在类 A 中,我们声明了一个属性 name 并在构造函数中赋值。而在类 B 中,我们使用参数属性,通过在构造函数参数前加上 public,直接在构造函数中为 name 赋值,省略了额外的初始化步骤。

使用参数属性的优势在于代码更加简洁,同时避免了在类中重复声明和初始化的操作。

静态属性

在 TypeScript 中,static 关键字用于定义类的静态属性和静态方法。静态成员属于类本身,而不是类的实例。它们可以直接通过类名访问,而不需要创建类的实例

typescript 复制代码
class MathOperations {
  static PI: number = 3.14;

  static add(x: number, y: number): number {
    return x + y;
  }
}

// 访问静态属性
console.log(MathOperations.PI); // 3.14

// 调用静态方法
const sum = MathOperations.add(2, 3);
console.log(sum); // 5

在这个例子中,MathOperations 类有一个静态属性 PI 和一个静态方法 add。可以直接通过类名 MathOperations.PI 访问静态属性,通过 MathOperations.add(2, 3) 调用静态方法。

需要注意的是,静态成员不能被实例继承,也不能通过类的实例直接访问。它们属于类本身,而不是类的实例

可选类属性

在 TypeScript 中,可以通过在类的属性声明后面加上 ? 符号来表示可选属性。可选属性意味着在创建类的实例时,可以选择性地为这些属性提供值,而不是必须提供。

typescript 复制代码
class Person {
  firstName: string;
  lastName?: string;

  constructor(firstName: string, lastName?: string) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName(): string {
    if (this.lastName) {
      return `${this.firstName} ${this.lastName}`;
    } else {
      return this.firstName;
    }
  }
}

// 可选属性lastName可以不提供
const person1 = new Person("John");
console.log(person1.getFullName()); // "John"

// 提供了可选属性lastName
const person2 = new Person("Jane", "Doe");
console.log(person2.getFullName()); // "Jane Doe"

在上面的例子中,lastName 属性被标记为可选属性,可以选择不在构造函数中提供它。在 getFullName 方法中,会检查 lastName 是否存在,如果存在则拼接成完整的姓名。

存取器

在 TypeScript 中,存取器(Accessors)是一种用于捕获对象属性值的读取和写入的方法。存取器由 getter 和 setter 组成,允许你在获取和设置属性值时执行自定义的逻辑。

typescript 复制代码
class UserInfo {
  private _fullName: string;

  get fullName(): string {
    return this._fullName;
  }

  set fullName(value: string) {
    console.log(`Setting full name to: ${value}`);
    this._fullName = value;
  }
}

const user = new UserInfo();
user.fullName = "John Doe"; // 设置 full name 到: John Doe
console.log(user.fullName); // John Doe

在这个例子中,fullName 是一个存取器属性,由 _fullName 支持。fullName 的 getter 方法用于获取 _fullName,而 setter 方法用于设置 _fullName。当你给 fullName 赋值时,实际上是调用了 setter 方法,同时在设置属性值的时候,会输出相应的信息。

存取器属性提供了更高级的控制,允许你在属性被访问或设置时执行自定义的逻辑。

抽象类

抽象类是 TypeScript 中一种特殊的类,它不能直接被实例化,通常用作其他类的基类。抽象类中可以包含抽象方法,这些方法在抽象类中只有声明,而没有具体实现。子类必须提供这些抽象方法的具体实现。

typescript 复制代码
abstract class People {
  constructor(public name: string) {}
  abstract printName(): void;
}
class Man extends People {
  constructor(name: string) {
    super(name);
    this.name = name;
  }
  printName() {
    console.log(this.name);
  }
}
const m = new Man(); // error 应有 1 个参数,但获得 0 个
const man = new Man("lison");
man.printName(); // 'lison'
const p = new People("lison"); // error 无法创建抽象类的实例

People 是一个抽象类,它有一个抽象方法 printName。抽象类不能被直接实例化,因此 People 类不能通过 new People("lison") 来创建实例。

然后,有一个继承自 People 的子类 Man。Man 类必须提供 printName 方法的具体实现,否则会报错。在 Man 类中,通过 super(name) 调用了父类的构造函数,并在构造函数中给 name 属性赋值。

现在,可以通过创建 Man 类的实例来使用这个类

ini 复制代码
const man = new Man("lison");
man.printName(); // 输出 'lison'

如果尝试直接实例化抽象类 People 会导致错误,因为抽象类不能被实例化。这样的设计是为了确保抽象类的方法只能通过子类来实现,并不能直接使用抽象类的实例。

arduino 复制代码
const p = new People("lison"); // 编译错误:无法创建抽象类的实例

抽象类在定义一些具有通用行为和结构的类时非常有用,而子类则负责提供具体实现


对于下述例子

scala 复制代码
abstract class People {
  constructor(public name: string) {}
  abstract printName(): void;
}
class Man extends People {
  // error 非抽象类"Man"不会实现继承自"People"类的抽象成员"printName"
  constructor(name: string) {
    super(name);
    this.name = name;
  }
}
const m = new Man("lison");
m.printName(); // error m.printName is not a function

在 Man 类的构造函数中,通过 super(name) 调用了父类的构造函数,并在构造函数中给 name 属性赋值。但由于未实现抽象方法 printName,会产生编译错误

scala 复制代码
// 编译错误:非抽象类"Man"不会实现继承自"People"类的抽象成员"printName"
class Man extends People {
  constructor(name: string) {
    super(name);
    this.name = name;
  }
}

此外,尝试创建 Man 类的实例并调用未实现的抽象方法会导致运行时错误:

ini 复制代码
const m = new Man("lison");
m.printName(); // 运行时错误:m.printName is not a function

抽象类的抽象方法必须在子类中实现,否则会在编译时或运行时产生错误。这确保了子类提供了对抽象类中声明的方法的具体实现。

TypeScript 2.0 版本引入的一个新特性,允许使用 abstract 关键字标记类中的属性和存取器

scala 复制代码
abstract class People {
  abstract _name: string;
  abstract get insideName(): string;
  abstract set insideName(value: string);
}
class Pp extends People {
  _name: string;
  insideName: string;
}
  • 抽象属性 _name:这个属性在抽象类中声明但没有提供具体的实现。它是一个占位符,要求子类提供具体的属性实现。
  • 抽象存取器 insideName:它包括了一个抽象的 get 存取器和一个抽象的 set 存取器。与抽象方法一样,这些存取器在抽象类中仅仅是声明了名称和类型,而没有提供具体的实现。

有一个继承自 People 的子类 Pp。在子类中,需要提供对抽象属性和抽象存取器的具体实现。在这里,_name 和 insideName 都被具体实现了。

需要注意的是,抽象属性和存取器都不能包含实际的代码块。它们的主要作用是在抽象类中定义一个占位符,强制要求子类提供相应的实现,以确保子类符合抽象类的约定。

类类型的接口

在 TypeScript 中,接口(Interfaces)可以被用来强制一个类的定义必须包含某些特定成员。这种通过接口来定义类的结构的方式可以帮助开发者在编码阶段捕获一些潜在的错误,并确保类符合指定的契约。

typescript 复制代码
interface FoodInterface {
  type: string;
}
class FoodClass implements FoodInterface {
  // error Property 'type' is missing in type 'FoodClass' but required in type 'FoodInterface'
  static type: string;
  constructor() {}
}

FoodClass 类使用了 FoodInterface 接口,并通过 implements 关键字来强制该类包含接口中定义的 type 属性。如果在 FoodClass 中没有实现 type 属性,编译器将会报错,提示缺少了必需的属性。

相关推荐
一生为追梦5 小时前
Linux 内存管理机制概述
前端·chrome
喝旺仔la5 小时前
使用vue创建项目
前端·javascript·vue.js
心.c5 小时前
植物大战僵尸【源代码分享+核心思路讲解】
前端·javascript·css·数据结构·游戏·html
喝旺仔la5 小时前
Element Plus中button按钮相关大全
前端·javascript·vue.js
柒@宝儿姐6 小时前
Git的下载与安装
前端·javascript·vue.js·git·elementui·visual studio
Hiweir ·6 小时前
机器翻译之数据处理
前端·人工智能·python·rnn·自然语言处理·nlp·机器翻译
曈欣7 小时前
vue 中属性值上变量和字符串怎么拼接
前端·javascript·vue.js
QGC二次开发7 小时前
Vue3:v-model实现组件通信
前端·javascript·vue.js·前端框架·vue·html
努力的小雨8 小时前
从设计到代码:探索高效的前端开发工具与实践
前端
小鼠米奇9 小时前
详解Ajax与axios的区别
前端·javascript·ajax