TypeScript - 类详解

本文详细探讨了TypeScript中的类机制,包括继承、访问修饰符public、private和protected的用法,以及readonly修饰符、存取器、静态属性、抽象类等特性。重点介绍了private和protected成员的访问限制,以及如何使用构造器和静态属性。同时,文章还提到了将类用作接口的高级技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类写法

class Greeter {
  greeting: string; //属性
  constructor(message: string) { //构造器:
      this.greeting = message;
  }
  greet() { //方法
      return "Hello, " + this.greeting;
  }
}

let greeter = new Greeter("world"); //通过new来创建Greeter的实例greeter(调用类里面的构造器来初始化)

继承 extends

class Animal {
  move(distanceInMeters: number = 0) {
      console.log(`Animal moved ${distanceInMeters}m.`);
  }
}

class Dog extends Animal {
  bark() {
      console.log('Woof! Woof!');
  }
}
const dog = new Dog();
dog.bark(); //Woof! Woof!
dog.move(10); //Animal moved 10m.
dog.bark();//Woof! Woof!

子类会继承父类里面所有的属性和方法,私有(前面带有private)的除外。因此作为子类Dog的实例dog除了可以调用自己的方法dark,也可以调用父类的方法。

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

class Snake extends Animal { 
  constructor(name: string) { super(name); }
  move(distanceInMeters = 5) { //重写父类的move
      console.log("Slithering...");
      super.move(distanceInMeters); //调用父类的move
  }
}

class Horse extends Animal { 
  constructor(name: string) { super(name); }
  move(distanceInMeters = 45) {
      console.log("Galloping...");
      super.move(distanceInMeters);
  }
}

let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino"); //虽然tom: Animal声明成Animal,但是new出来的是Horse的实例。所以调用的还是horse里面重写了的move

sam.move();//Slithering...   Sammy the Python moved 5m.
tom.move(34);//Galloping...   Tommy the Palomino moved 34m.

:当子类包含了一个构造函数constructor,它必须调用super(),它会执行父类的构造函数。 而且,在构造函数里访问this的属性之前,我们一定要调用super()。 这是一条TypeScript强制执行的重要规则。

public, private 和 protected

默认是public

在TypeScript里,默认成员是public的。也可以手动添加。

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

ECMAScript Private句法

TypeScript 3.8支持新的JavaScript的private写法

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

new Animal("Cat").#name; // Property '#name' is not accessible outside class 'Animal' because it has a private identifier.

理解TypeScript中的private

在TypeScript中通过添加private来把成员声明成私有,这样以来除class本身以外不可访问。即便是继承的子类。

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

new Animal("Cat").name; //Error: 'name' is private
new Animal("dog").getName(); //只能通过内部方法来调用私有成员变量

在TypeScript中,当我们对比两种不同的类型,无论它们来自哪,只要所有成员的类型是兼容的,我们就认为它们的类型是兼容的。

但是在比较具有private和protected的成员类型时,如果其中一个类型具有private,那么另一个必须也具有源自于同一声明的private成员。同样是规则也适用于protected

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
    getName (){
      console.log(this.name);
    }
  }
  
  class Rhino extends Animal {
    constructor() { super("Rhino"); }
    test (){
      console.log(this.name); //Error: name 是私有的, 只能通过class Animal访问
    }
  }
  
  class Employee {
    private name: string; //虽然也是私有变量name,但是不是声明在Animal里的name成员。所以animal = employee;会报错
    constructor(theName: string) { this.name = theName; }
  }
  
  let animal = new Animal("Goat");
  let rhino = new Rhino();
  let employee = new Employee("Bob");

  animal.getName(); //Goat
  rhino.getName(); //Rhino 注意Rhino只是和Animal分享一个name声明,并不是继承了Animal的name
  
  animal = rhino;
  animal = employee; // Error: 'Animal' 和 'Employee' 不兼容

理解TypeScript中的protected

protected的行为与private非常相似,区别在于子类可以访问protected成员,但是不能访问private成员

: protected, private只能通过class内部的方法访问,创建的实例是无法访问的

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

class Employee extends Person {
  private department: string;

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

  public getElevatorPitch() {
      return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // error

构造器同样可以声明成protected. 一旦声明成protected, 除了类本身以外无法使用,但是可以被子类扩展使用。

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

// Employee can extend Person
class Employee extends Person {
  private department: string;

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

  public getElevatorPitch() {
      return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // Error: The 'Person' constructor is protected

ReadOnly 修饰符

只读属性必须在其声明或构造函数中进行初始化。

class Octopus {
  readonly name: string;
  readonly numberOfLegs: number = 8;
  readonly age: number;
  constructor (theName: string) {
      this.name = theName;
      this.age = 18; //correct
  }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name //Man with the 8 strong legs
dad.numberOfLegs //8
dad.age //18

参数属性

在上一个例子中,我们先定义了一个只读成员name. 但是没有赋值。需要在构造器里面写this.name= theName; 这样才能够让name在创建实例(也就是创造器执行)的时候得到值。

通过在构造器里声明 readonly name: string 参数我们可以简写上面的例子

class Octopus {
  readonly numberOfLegs: number = 8;
  readonly age: number;
  constructor (readonly name: string) {  }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name; //Man with the 8 strong legs


这样的写法也同样适用于private, protected, public;
class Octopus {
  constructor(private name: string, protected work: string, public hobby: string) {
  }

  getName() {
    console.log(this.name);
  }
  getWork() {
    console.log(this.work);
  }
}
let Lilei = new Octopus("lilei", "IT", "Play game");
Lilei.name; //Error
Lilei.work; //Error
Lilei.hobby; //Play game
Lilei.getName; //lilei
Lilei.getWork; //IT

存取器Accessors

通过设置getter & setter方式去截取对一个对象的成员访问。

普通写法

class Employee {
  fullName: string;
}

let employee = new Employee();
employee.fullName = "Bob Smith";

if (employee.fullName) {
  console.log(employee.fullName);
}

getter & setter写法

const fullNameMaxLength = 10;

class Employee {
private name: string;

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

    set fullName(newName: string) {
        if (newName && newName.length > fullNameMaxLength) {
            throw new Error("fullName has a max length of " + fullNameMaxLength);
        }name
        
        this.name = newName;
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    console.log(employee.fullName);
}

关于存取器的一些注意事项
编译器版本 > = ECMAScript 5, ECMAScript 3及以下不支持
只带有get不带有set的存取器自动识别为readonly. 这在从代码生成.d.ts文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值.

静态属性Static Properties

  • 通过this在class内部调用 e.g this.scale
  • 通过class.property 调用 e.g Grid.origin
class Grid {
  static origin = {x: 0, y: 0};
  calculateDistanceFromOrigin(point: {x: number; y: number;}) {
      let xDist = (point.x - Grid.origin.x);
      let yDist = (point.y - Grid.origin.y);
      return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
  }
  constructor (public scale: number) { }
}

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale
console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10})); //12.806248474865697
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10})); //2.5612496949731396
console.log(Grid.origin); //{x: 0, y: 0}

抽象类Abstract Classes

用关键字abstract定义一个抽象类

abstract class Department {
  constructor(public name: string) {}

  printName(): void {
    console.log("Department name: " + this.name);
  }

  abstract printMeeting(): void; // 抽象方法没有方法体,必须在子类中实现
}

class AccountingDepartment extends Department {
  constructor() {
    super("Accounting and Auditing"); // 子类必须通过调用super()来调用父类的构造器
  }

  printMeeting(): void {
    console.log("The Accounting Department meets each Monday at 10am.");
  }

  generateReports(): void {
    console.log("Generating accounting reports...");
  }
}

let department: Department; // 可以创建对抽象类的引用: P.s.并不懂这个用来干嘛,求解惑
// department = new Department(); // error: 不能创建抽象类的实例
department = new AccountingDepartment(); 
department.printName();
department.printMeeting();
department.generateReports(); // error: 声明的抽象类型不存在该方法

知识总结:

  • 抽象方法没有方法体,必须要在子类里实现
  • 抽象类没有实例
  • 抽象类的子类里面定义的正常方法不能被该类实例调用

高级技巧

构造器函数

正常类写法

class Greeter {
  greeting: string;
  constructor(message: string){
    this.greeting = message;
  }

  greet(){
    return "Hello, " + this.greeting;
  }
}

let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet());

用原生js实现

let Greeter = (function() {
  function Greeter(message) {
    this.greeting = message;
  }
  Greeter.prototype.greet = function() {
    return "Hello, " + this.greeting;
  };
  return Greeter;
})();

let greeter;
greeter = new Greeter("world");
console.log(greeter.greet()); // "Hello, world"
class Greeter {
  static standardGreeting = "Hello, there";
  greeting: string;
  greet() {
    if (this.greeting) {
      return "Hello, " + this.greeting;
    } else {
      return Greeter.standardGreeting;
    }
  }
}

let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet()); // "Hello, there"

let greeterMaker: typeof Greeter = Greeter; //typeof Greeter 取Greeter类本身的类型,而不是实例的类型。(我试了,不写也是可以的)更确切的说是获取构造函数的类型。该类型包含了类的所有静态成员和以及创建Greeter类实例的构造函数。
greeterMaker.standardGreeting = "Hey there!";

let greeter2: Greeter = new greeterMaker(); //创建Greeter的实例
console.log(greeter2.greet()); // "Hey there!"

把一个类当作一个接口使用

类声明创建了两件事:代表类实例的类型和构造函数。 因为类创建类型,所以可以在能够使用接口的位置使用它们。

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

interface Point3d extends Point {
  z: number;
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值