当前位置: 首页 > news >正文

TypeScript---class类型

一.简介

TypeScript 完全支持 ES2015 中引入的 class 关键字。

与其他 JavaScript 语言功能一样,TypeScript 添加了类型注释和其他语法,以允许你表达类和其他类型之间的关系。

1.字段

(1).在申明时同时给出类型
class Person {name: string;age: number;
}

(2).申明时不给出类型

如果不给出类型,那么typescript会自动推断为any类型的数据

class Person {name; // any类型age; // any类型
}

(3).声明时给出初值

typescript会自动推断出来数据的类型是什么,比如下面的就会推断出来数据的类型为string与number

class Person {name = "寻谬~流光"; // stringage = 30; // number
}

(4).打开了TypeScript 的配置项strictPropertyInitialization

只要打开,就会检查属性是否设置了初值,如果没有就报错。

如果你打开了这个设置,但是某些情况下,不是在声明时赋值或在构造方法里面赋值,为了防止这个设置报错,可以使用非空断言。

class person {name!: string;age!: number;
}

属性名后面添加了感叹号,表示这两个属性肯定不会为空,所以 TypeScript 就不报错了

2.readonly 修饰符

属性名前面加上 readonly 修饰符,就表示该属性是只读的。实例对象不能修改这个属性。

  1. 属性前面有 readonly 修饰符,实例对象修改这个属性就会报错。
  2. 构造方法内部可以设置只读属性的初值
  3. 构造方法可以修改只读属性的值
class Person {readonly name = "寻谬~流光";readonly age!: number;// 哎构造函数内部可以自由修改readonly属性,不管是已经赋值了的,或者是没有赋值的数据类型constructor(age: number) {this.age = age;this.age = 20;}
}const p = new Person(18);
console.log(p.name); // 寻谬~流光
console.log(p.age); // 20p.age = 25; // 编译器报错, 不能对readonly属性重新赋值

3.构造器

constructor 方法是 class 的一个特殊方法,用于创建和初始化该类的对象实例。

类构造函数与函数非常相似。你可以添加带有类型注释、默认值和重载的参数

class Person {name = "寻谬~流光";age!: number;//通过给参数设置默认值,实现类似重载的效果。constructor(age: number = 18, name: string = "寻谬~流光") {this.age = age;this.name = name;}
}let p1 = new Person();
let p2 = new Person(20, "寻觅~流光");
let p3 = new Person(undefined, "你好");console.log(p1); // Person { name: '寻谬~流光', age: 18 }
console.log(p2); // Person { name: '寻觅~流光', age: 20 }
console.log(p3); // Person { name: '你好', age: 18 }

4.方法

类上的函数属性称为方法。

类的方法就是普通函数,类型声明方式与函数一致。

class Person {name = "寻谬~流光";age!: number;constructor(age: number = 18, name: string = "寻谬~流光") {this.age = age;this.name = name;}// 不添加函数返回值的类型,typescript会自动推导出来这个函数返回的数据类型((method) Person.add_age(num: number): number)add_age(num: number) {this.age += num;return this.age;}// 添加类型注释low_age(num: number = 1): number {this.age -= num;return this.age;}
}

5.获取器/设置器

存取器(accessor)是特殊的类方法,包括取值器(getter)和存值器(setter)两种方法。

它们用于读写某个属性,取值器用来读取属性,存值器用来写入属性。

class C {_name = "";get name() {return this._name;}set name(value) {this._name = value;}
}

上面示例中,get name()是取值器,其中get是关键词,name是属性名。外部读取name属性时,实例对象会自动调用这个方法,该方法的返回值就是name属性的值。

set name()是存值器,其中set是关键词,name是属性名。外部写入name属性时,实例对象会自动调用这个方法,并将所赋的值作为函数参数传入。

(1).如果某个属性只有get方法,没有set方法,那么该属性自动成为只读属性。
class Person {_name: string;_age: number;constructor(name: string, age: number) {(this._name = name), (this._age = age);}// name为只读属性get name(): String {return this._name + "___ABC";}
}
const p = new Person("Tom", 20);
console.log(p.name); // Tom___ABCp.name = "Jerry"; // 报错,不能修改只读属性

(2).set方法的参数类型,必须兼容get方法的返回值类型,否则报错。
class Person {_name: string;_age: number;constructor(name: string, age: number) {(this._name = name), (this._age = age);}get name(): string {return this._name + "___ABC";}// 正确写法// set name(value: string) {//   this._name = value;// }// 报错set name(value: number) {this._name = value;}
}

(3).get方法与set方法的可访问性必须一致,要么都为公开方法,要么都为私有方法。

正确写法:

class Person {_name: string;_age: number;constructor(name: string, age: number) {(this._name = name), (this._age = age);}private get name(): string {return this._name + "___ABC";}private set name(value: string) {this._name = value;}
}

错误写法:

class Person {_name: string;_age: number;constructor(name: string, age: number) {(this._name = name), (this._age = age);}private get name(): string {return this._name + "___ABC";}// Get 访问器必须至少具有与 Setter 相同的可访问性public set name(value: string) {this._name = value;}
}

6.索引签名

类可以声明索引签名;这些工作与 其他对象类型的索引签名 相同:

类允许定义属性索引。

class MyClass {[s: string]: boolean | ((s: string) => boolean);get(s: string) {return this[s] as boolean;}
}

上面示例中,[s:string]表示所有属性名类型为字符串的属性,它们的属性值要么是布尔值,要么是返回布尔值的函数。

注意,由于类的方法是一种特殊属性(属性值为函数的属性),所以属性索引的类型定义也涵盖了方法。如果一个对象同时定义了属性索引和方法,那么前者必须包含后者的类型。

class MyClass {[s: string]: boolean;f() {// 报错return true;}
}

上面示例中,属性索引的类型里面不包括方法,导致后面的方法f()定义直接报错。正确的写法是下面这样。

class MyClass {[s: string]: boolean | (() => boolean);f() {return true;}
}

属性存取器等同于方法,也必须包括在属性索引里面。

class MyClass {[s: string]: boolean;get(s: string) {// 报错return this[s] as boolean;}
}

上面示例中,属性索引没有给出方法的类型,导致get()方法报错。

二.类的 interface 接口

1.implements 关键字

interface 接口或 type 别名,可以用对象的形式,为 class 指定一组检查条件。然后,类使用 implements 关键字,表示当前类满足这些外部类型条件的限制。

interface Person {name: string;age: number;
}// 或者这种写法
// type Person = {
//   name: string;
//   age: number;
// };class Student implements Person {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}
}

 你可以使用 implements 子句来检查一个类是否满足特定的 interface。如果一个类未能正确实现它,则会触发错误:

interface Pingable {ping(): void;
}class Sonar implements Pingable {ping() {console.log("ping!");}
}class Ball implements Pingable {pong() {console.log("pong!");}
}

implements关键字后面,不仅可以是接口,也可以是另一个类。这时,后面的类将被当作接口。

class Person {name: string;age: number;add: (value: number) => number;
}// 实现接口
class Student implements Person {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}add(value: number) {this.age += value;return this.age;}
}

注意,interface 描述的是类的对外接口,也就是实例的公开属性和公开方法,不能定义私有的属性和方法。这是因为 TypeScript 设计者认为,私有属性是类的内部实现,接口作为模板,不应该涉及类的内部代码写法。

2.实现多个接口

类可以实现多个接口(其实是接受多重限制),每个接口之间使用逗号分隔。

interface student {//...
}
interface teacher {// ...
}// 继承多个接口
class class_people implements student, teacher {// ...
}

3.类与接口的合并

TypeScript 不允许两个同名的类,但是如果一个类和一个接口同名,那么接口会被合并进类。

interface person {name: string;
}
class person {age: number;adress: string;id: number;
}const p: person = {name: "Tom",age: 25,adress: "China",id: 123456,
};console.log(p.name); // Tom

就比如上

定义了person的接口与person的类,接口定义的会被合并到class的类型定义中去

三.类继承

1.extends 从句

类可能来自基类。派生类具有其基类的所有属性和方法,还可以定义额外的成员。

class father {name: string;age: number;
}
class son extends father {name: string;age: number;adress: string;
}
let s = new son();
s.name = "Tom";
s.age = 20;
s.adress = "China";
console.log(s);
  • super(name, age):在子类构造函数中,必须先调用父类的构造函数,才能使用 this
  • 如果子类重写了父类方法,也可以在方法中用 super.方法名() 调用父类的方法。

2.super语句

super 语句用于在子类中调用父类的构造函数或父类的方法。

class Father {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}
}class Son extends Father {adress: string;constructor(name: string, age: number, adress: string) {super(name, age); // 调用父类构造函数this.adress = adress;}
}const s = new Son("Tom", 20, "China");

3.override语句

在 TypeScript 中,override 关键字用于明确表示子类正在重写父类的成员方法或属性。这样可以让编译器检查你是否真的在重写父类已有的方法,防止拼写错误或父类没有该方法时出错。

class Father {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}sayHello() {console.log(`Hello, I am ${this.name}`);}
}class Son extends Father {adress: string;constructor(name: string, age: number, adress: string) {super(name, age); // 调用父类构造函数this.adress = adress;}// 重写父类方法override sayHello() {super.sayHello(); // 可选:调用父类方法console.log(`I live in ${this.adress}`);}
}const s = new Son("Tom", 20, "China");
s.sayHello();
// 输出:
// Hello, I am Tom
// I live in China
  • override 只能用于重写父类已有的方法或属性。
  • 如果父类没有 sayHello 方法,写 override sayHello() 会报错,防止误写。
  • 推荐在所有重写父类成员时加上 override,代码更规范、安全。

四.成员可见性

类的内部成员的外部可访问性,由三个可访问性修饰符(access modifiers)控制:publicprivateprotected

这三个修饰符的位置,都写在属性或方法的最前面。

1.public

(1). 基本用法

public修饰符表示这是公开成员,外部可以自由访问。

正常情况下,除非为了醒目和代码可读性,public都是省略不写的。

class Person {public name: string;     // 显式声明为publicage: number;             // 隐式public(默认)public constructor(name: string, age: number) {this.name = name;this.age = age;}public greet() {        // 方法默认也是publicreturn `Hello, I'm ${this.name}`;}
}const person = new Person("Alice", 30);
console.log(person.name);  // 合法:外部可访问public成员
person.greet();            // 合法

2.protected

(1). 基本用法

protected修饰符表示该成员是保护成员,只能在类的内部使用该成员,实例无法使用该成员,但是子类内部可以使用。

class Animal {protected name: string;constructor(name: string) {this.name = name;}protected move(distance: number) {console.log(`${this.name} moved ${distance}m.`);}
}class Dog extends Animal {constructor(name: string) {super(name);}bark() {console.log(`${this.name} barks!`); // 合法:子类可访问protected成员this.move(5);                      // 合法:子类可调用protected方法}
}const dog = new Dog("Buddy");
// console.log(dog.name); // 错误:实例无法访问protected成员
// dog.move(10);          // 错误
dog.bark();              // 合法

(2).子类不仅可以拿到父类的保护成员,还可以定义同名成员。
class Vehicle {protected speed: number = 0;protected accelerate(value: number) {this.speed += value;}
}class Car extends Vehicle {protected speed: number = 10; // 子类可以定义同名protected成员override accelerate(value: number) {this.speed += value * 2;   // 子类可以重写protected方法}
}

(3).在类的外部,实例对象不能读取保护成员,但是在类的内部可以。

3.private

(1). 基本用法

private修饰符表示私有成员,只能用在当前类的内部,类的实例和子类都不能使用该成员。

class BankAccount {private balance: number = 0;constructor(initialDeposit: number) {this.balance = initialDeposit;}public deposit(amount: number) {this.balance += amount; // 合法:类内部可访问private成员}public getBalance() {return this.balance;    // 合法}
}const account = new BankAccount(1000);
// console.log(account.balance); // 错误:无法访问private成员
account.deposit(500);
console.log(account.getBalance()); // 合法:通过public方法访问

(2).子类不能定义父类私有成员的同名成员。
class Base {private code: string = "123";
}class Derived extends Base {// private code: number = 456; // 错误:不能重定义父类私有成员
}

(3).如果在类的内部,当前类的实例可以获取私有成员。

4.实例属性的简写形式

在构造函数参数前加上 publicprotected 或 private,TypeScript 会自动为你声明并初始化同名实例属性。

这种写法更简洁,常用于数据类或 DTO(数据传输对象)。

这样可以让代码更简洁、易读。

class Config {constructor(public apiKey: string,public timeout: number = 5000,private debug?: boolean) {}
}const config = new Config("123-456");
console.log(config.timeout); // 输出: 5000 (默认值)

五.静态成员

类的内部可以使用static关键字,定义静态成员。

静态成员的特点:

  1. 包括静态属性和静态方法,只能通过类名直接访问,无法通过实例调用。
  2. 类加载时创建,仅一份
  3. 子类可以继承父类的静态成员,也可以重写静态方法
class Person {static value = 1;constructor(public name: string) {}log_value() {console.log(Person.value);}add_value(num: number) {Person.value += num;}
}// 只可以通过类名直接访问到静态属性和方法
// 这里静态方法同样如此,就不写了
console.log(Person.value);let p: Person = new Person("Tom");
let p2: Person = new Person("Jerry");p.log_value(); // 1
p2.log_value(); // 1p.add_value(2);p.log_value(); // 3
p2.log_value(); // 3p2.add_value(3);p.log_value(); // 6
p2.log_value(); // 6

六.抽象类,抽象成员

TypeScript 允许在类的定义前面,加上关键字abstract,表示该类不能被实例化,只能当作其他类的模板。这种类就叫做“抽象类”(abastract class)。

1.抽象类只能当作基类使用,用来在它的基础上定义子类。

不能实例化抽象类

abstract class Animal {constructor(public name: string, public age: number) {}
}// const Animal_1 = new Animal("Animal", 1); // 错误! 不能实例化抽象类class Dog extends Animal {constructor(name: string, age: number, public breed: string) {super(name, age); // 实现构造函数}bark() {console.log(`${this.name} is barking.`);}
}

2.抽象类的子类也可以是抽象类,也就是说,抽象类可以继承其他抽象类。

abstract class Animal {constructor(public name: string, public age: number) {}
}abstract class Dog extends Animal {constructor(name: string, age: number, public breed: string) {super(name, age);}sound(): void {console.log("W-T-F !");}
}class Labrador extends Dog {constructor(name: string, age: number) {super(name, age, "Labrador");}bark(): void {console.log("Woof, woof!");}
}const myDog = new Labrador("Buddy", 3);
myDog.sound(); // 输出: W-T-F !

3.抽象成员

如果抽象类的属性前面加上abstract,就表明子类必须给出该方法的实现。

abstract class Animal {constructor(public name: string, public age: number) {}abstract makeSound(): void; // 抽象成员函数
}class Dog extends Animal {// 实现抽象成员函数makeSound() {console.log("w t f!");}
}

(1)抽象成员只能存在于抽象类,不能存在于普通类。

(2)抽象成员不能有具体实现的代码。也就是说,已经实现好的成员前面不能加abstract关键字。

(3)抽象成员前也不能有private修饰符,否则无法在子类中实现该成员。

(4)一个子类最多只能继承一个抽象类。

总之,抽象类的作用是,确保各种相关的子类都拥有跟基类相同的接口,可以看作是模板。其中的抽象成员都是必须由子类实现的成员,非抽象成员则表示基类已经实现的、由所有子类共享的成员。

七.this 问题

在类中,this 始终指向当前实例对象,但这个指向会受到调用方式的影响。

  • 当通过 实例.方法() 调用时,this 正常指向实例;
  • 当方法被单独提取(如赋值给变量、作为回调)时,this 会丢失指向(变成 undefined 或全局对象)。

这是因为类的方法默认定义在原型上,调用时需要依赖调用者来确定 this 的指向(即 “谁调用,this 指向谁”)。

1.常见的this场景问题

(1).方法作为回调函数时,this 丢失

原因callback 只是一个函数引用,调用时没有 “调用者”,this 无法指向 user 实例。

class User {name: string = "Alice";sayHi() {console.log(`Hi, I'm ${this.name}`); // 这里的 this 会在回调中丢失!}
}const user = new User();
// 直接调用:this 指向 user 实例(正常)
user.sayHi(); // 输出:Hi, I'm Alice// 将方法提取为回调函数
const callback = user.sayHi; 
// 单独调用时,this 指向 undefined(严格模式下)
callback(); // 报错:Cannot read property 'name' of undefined

(2).事件监听中的 this 丢失

原因:DOM 事件回调中,this 默认指向触发事件的 DOM 元素,覆盖了类实例的 this

class Button {label: string = "Click me";onClick() {console.log(`Clicked ${this.label}`); // 事件触发时,this 指向触发事件的 DOM 元素,而非实例}
}const btn = new Button();
// 给按钮绑定点击事件(假设页面有个 id 为 "btn" 的按钮)
document.getElementById("btn")?.addEventListener("click", btn.onClick);// 当点击按钮时:
// 输出:Clicked undefined(因为 this 指向按钮 DOM 元素,而非 Button 实例)

(3).箭头函数与普通方法的 this 差异

原因:箭头函数没有自己的 this,它的 this 是定义时捕获的上下文(此处即类实例)。

class Test {value: number = 10;// 普通方法:this 依赖调用者normalMethod() {console.log(this.value);}// 箭头函数方法:this 固定指向实例arrowMethod = () => {console.log(this.value);};
}const test = new Test();
const normal = test.normalMethod;
const arrow = test.arrowMethod;normal(); // 报错:this 丢失
arrow();  // 正常输出:10(this 始终指向 test 实例)

2.解决 this 指向问题的 3 种方案

(1).在构造函数中绑定 this(最常用)

通过 this.方法 = this.方法.bind(this) 强制绑定 this 到实例:

class User {name: string = "Alice";constructor() {// 绑定 this 到当前实例this.sayHi = this.sayHi.bind(this);}sayHi() {console.log(`Hi, I'm ${this.name}`);}
}const user = new User();
const callback = user.sayHi;
callback(); // 正常输出:Hi, I'm Alice(this 已绑定)

(2).使用箭头函数定义方法(简洁)

直接将方法定义为箭头函数,利用箭头函数的 this 特性固定指向:

class Button {label: string = "Click me";// 箭头函数方法:this 永远指向实例onClick = () => {console.log(`Clicked ${this.label}`);};
}const btn = new Button();
document.getElementById("btn")?.addEventListener("click", btn.onClick);
// 点击时输出:Clicked Click me(this 正确指向 Button 实例)

(3).调用时通过 call/apply 指定 this

在调用方法时,用 call 或 apply 手动指定 this

class User {name: string = "Bob";sayHi() {console.log(`Hi, I'm ${this.name}`);}
}const user = new User();
const callback = user.sayHi;// 调用时用 call 绑定 this
callback.call(user); // 输出:Hi, I'm Bob

八.泛型类

类也可以写成泛型,使用类型参数。关于泛型的详细介绍

class person<T> {name: T;age: number;constructor(name: T, age: number) {this.name = name;this.age = age;}
}let p1: person<string> = new person("Tom", 25);
console.log(p1.name); // output: Tom

注意,静态成员不能使用泛型的类型参数。

下一篇:TypeScript---泛型

http://www.lryc.cn/news/584996.html

相关文章:

  • 工业通信升级新选择:耐达讯CCLINKIE转Modbus TCP网关
  • 猿人学js逆向比赛第一届第十九题
  • U-Net网络学习笔记(1)
  • 2025亚太中文赛项 B题疾病的预测与大数据分析保姆级教程思路分析
  • 机器学习数据集加载全攻略:从本地到网络
  • 【读代码】开源音乐分离工具Spleeter
  • 深度学习14(循环神经网络)
  • 深度学习篇---昇腾NPUCANN 工具包
  • JVM故障处理与类加载全解析
  • 数据结构自学Day5--链表知识总结
  • 大规模集群下 Prometheus 监控架构实战经验分享
  • LTR相关记录
  • 牛客周赛 Round 99
  • 【Dify(v1.x) 核心源码深入解析】mcp 模块
  • 4.丢出异常捕捉异常TryCatch C#例子
  • USB数据丢包真相:为什么log打印会导致高频USB数据丢包?
  • mysql数据库导入导出命令
  • 【Linux-云原生-笔记】系统引导修复(grub、bios、内核、系统初始化等)
  • Grok-4 发布会图文总结
  • 苹果UI 设计
  • SLICEGPT: COMPRESS LARGE LANGUAGE MODELSBY DELETING ROWS AND COLUMNS
  • Deepseek-如何从零开始开发需要专业知识的prompt
  • 8155平台SPI学习笔记
  • 从零实现一个GPT 【React + Express】--- 【4】实现文生图的功能
  • 深入剖析Spring Bean生命周期:从诞生到消亡的全过程
  • 英文国际期刊推荐:MEDS Chinese Medicine,中医药方向可发
  • 47-RK3588 用瑞芯微官方提供recovery进行OTA升级
  • Auto-GPT 简易教程
  • 前端抓包(不启动前端项目就能进行后端调试)--whistle
  • UI前端与数字孪生融合新领域:智慧环保的垃圾分类与回收系统