第 1 节 概述
上篇我们讲了函数。函数是好东西,但数据(属性)和行为(方法)往往是绑定在一起的。
类就是把相关的属性和方法打包成一个单元的工具。
JavaScript 在 ES6 引入了 class 语法。TypeScript 在此基础上做了重要增强,让它有了真正的面向对象体验。
第 2 节 类的基本写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
greet() {
return `你好,我是${this.name}`
}
}
const person = new Person("张三", 18)
console.log(person.greet()) // "你好,我是张三"
|
属性必须提前声明——这是和 JavaScript 的主要区别。JS 中可以直接 this.name = name,TS 中要先声明类型。
第 3 节 属性修饰符
TypeScript 提供了三种访问修饰符控制属性的可见性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| class Person {
public name: string // 公开,默认
protected age: number // 受保护,自己和子类可访问
private idCard: string // 私有,仅自己可访问
readonly id: number // 只读
constructor(name: string, age: number, idCard: string, id: number) {
this.name = name
this.age = age
this.idCard = idCard
this.id = id
}
}
class Student extends Person {
showInfo() {
console.log(this.name) // ✅ public,可访问
console.log(this.age) // ✅ protected,子类可访问
console.log(this.idCard) // ❌ private,子类不可访问
}
}
const p = new Person("张三", 18, "123456", 1)
console.log(p.name) // ✅ 外部可访问
console.log(p.age) // ❌ protected,外部不可访问
console.log(p.idCard) // ❌ private,外部不可访问
p.id = 2 // ❌ readonly,不可修改
|
| 修饰符 | 类内部 | 子类 | 外部 |
|---|
private | ✅ | ❌ | ❌ |
protected | ✅ | ✅ | ❌ |
public | ✅ | ✅ | ✅ |
readonly | 独立维度,与三者组合使用 | | |
第 4 节 参数属性
TypeScript 提供了一种简写方式——在构造函数参数前加修饰符,声明和赋值一步完成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // 传统写法
class Person {
private name: string
constructor(name: string) {
this.name = name
}
}
// 参数属性写法 ✨
class Person {
constructor(
private name: string, // 声明 + 赋值一步到位
public readonly age: number,
protected email?: string,
) {}
}
const p = new Person("张三", 18)
// 属性已经可用,代码更简洁
|
技巧
参数属性是 TypeScript 的特色语法。日常开发推荐多用这种写法,能省掉大量重复的属性声明代码。
第 5 节 继承
extends 实现类的继承:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| class Animal {
constructor(public name: string) {}
move(distance: number) {
console.log(`${this.name} 移动了 ${distance} 米`)
}
}
class Dog extends Animal {
constructor(name: string, public breed: string) {
super(name) // 必须调用 super
}
bark() {
console.log("汪汪!")
}
// 重写父类方法
move(distance: number) {
console.log(`${this.name} 跑起来了`)
super.move(distance) // 调用父类方法
}
}
const dog = new Dog("旺财", "金毛")
dog.bark() // "汪汪!"
dog.move(10) // "旺财 跑起来了" / "旺财 移动了 10 米"
|
- 子类构造函数必须调用
super() - 子类可以重写父类方法
super.xxx 调用父类方法
第 6 节 抽象类
抽象类是一种不能直接实例化的类。它定义了一组子类必须实现的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| abstract class Animal {
constructor(public name: string) {}
// 普通方法 —— 子类可以直接用
move(distance: number) {
console.log(`${this.name} 移动了 ${distance} 米`)
}
// 抽象方法 —— 子类必须实现
abstract makeSound(): void
}
// const a = new Animal("动物") // ❌ 不能实例化抽象类
class Dog extends Animal {
// 必须实现抽象方法
makeSound() {
console.log("汪汪!")
}
}
class Cat extends Animal {
makeSound() {
console.log("喵喵~")
}
}
|
抽象类与接口的区别:
| 对比 | 抽象类 | 接口 |
|---|
| 能否包含实现 | ✅ 可以 | ❌ 纯抽象 |
| 单继承 | 一个类只能继承一个抽象类 | 一个类可以实现多个接口 |
| 构造函数 | ✅ 可以有 | ❌ |
| 属性修饰符 | ✅ public / protected / private | ❌ |
第 7 节 接口实现
类可以实现(implements)一个或多个接口,保证类满足接口约定的结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| interface Flyable {
fly(): void
land(): void
}
interface Swimmable {
swim(): void
}
class Duck implements Flyable, Swimmable {
fly() {
console.log("鸭子飞起来了")
}
land() {
console.log("鸭子降落了")
}
swim() {
console.log("鸭子在游泳")
}
}
|
接口可以约束类的实例结构,也可以约束构造函数(通过构造签名):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| interface AnimalClass {
new (name: string): Animal // 构造签名
}
function createAnimal(cls: AnimalClass, name: string) {
return new cls(name)
}
class Dog {
constructor(public name: string) {}
}
class Cat {
constructor(public name: string) {}
}
const dog = createAnimal(Dog, "旺财")
const cat = createAnimal(Cat, "咪咪")
|
说明
接口 implements 是结构性检查——不要求显式声明 implements,只要结构匹配即可。但显式写出来能让代码意图更清晰。
类让你能把数据和行为组织在一起。但这里有个问题:如果你不确定某些数据类型,但又想复用同一段逻辑,该怎么办?
下一篇讲泛型——它让"不确定"变成"灵活且安全"。