一、认识类
在早期的 JavaScript 开发中(ES5)需要通过函数和原型链来实现类和继承
从 ES6 开始,引入了 class 关键字,可以更加方便的定义和使用类。
TypeScript 作为 JavaScript 的超集,也是支持使用 class 关键字的,并且还可以对类的属性和方法等进行静态类型检测
实际上在 JavaScript 的开发过程中,我们更加习惯于函数式编程:
- 比如 React 开发中,目前更多使用的函数组件以及结合 Hook 的开发模式
- 比如在 Vue3 开发中,目前也更加推崇使用 Composition API
但是在封装某些业务的时候,类具有更强大封装性,所以我们也需要掌握它们
- 类的定义我们通常会使用 class 关键字
- 在面向对象的世界里,任何事物都可以使用类的结构来描述
- 类中包含特有的属性和方法
二、类的定义
定义一个 Person 类: 使用 class 关键字来定义一个类
我们可以声明一些类的属性:
在类的内部声明类的属性以及对应的类型,如果类型没有声明,那么它们默认是 any 的
我们也可以给属性设置初始化值
在默认的 strictPropertyInitialization 模式下面我们的属性是必须初始化的,如果没有初始化,那么编译时就会报错
1 2 3 4 5 6 7 8
| class Person { name: string; age: number;
say() { console.log(`Hello,${this.name}`); } }
|

如果我们在 strictPropertyInitialization 模式下确实不希望给属性初始化,可以使用 name!: string 语法
类可以有自己的构造函数 constructor,当我们通过 new 关键字创建一个 实例时,构造函数会被调用
构造函数不需要返回任何值,默认返回当前创建出来的实例
类中可以有自己的函数,定义的函数称之为方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Person {
name: string; age: number; constructor(name: string, age: 20) { this.name = name; this.age = age; } say() { console.log(`Hello,${this.name}`); } } let p = new Person("xxx", 20);
|
三、类的特性
继承
面向对象的其中一大特性就是继承,继承不仅仅可以减少我们的代码量,也是多态的使用前提
我们使用 extends 关键字来实现继承,子类中使用 super 来访问父类
看一下 Student 类继承自 Person:
Student 类可以有自己的属性和方法,并且会继承 Person 的属性和方法
在构造函数中,我们可以通过 super 来调用父类的构造方法,对父类中的属性进行初始化;
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 28 29 30 31
| class Person { name: string; age: number;
constructor(name: string, age: number) { this.name = name; this.age = age; } eating() { console.log("eating"); } } class Student extends Person { son: number; constructor(name: string, age: number, son: number) { super(name, age); this.son = son; } eating() { super.eating(); console.log("student eating"); } } const stu1 = new Student("yyy", 20, 111);
console.log(stu1.name); console.log(stu1.age); stu1.eating();
|
多态
父类或者接口定义的引用变量可以指向子类或者具体实现类的实例对象
由于程序调用方法是在运行期才动态绑定的,那么引用变量所指向的具体实例对象在运行期才确定
所以这个对象的方法是运行期正在内存运行的这个对象的方法而不是引用变量的类型中定义的方法。
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
| class Animals { action() { console.log("action"); } } class Dog { action() { console.log("dog running"); } }
class Fish { action() { console.log("fish swimming"); } }
function moveAction(animals: Animals[]) { animals.forEach((animal) => { animal.action(); }); }
moveAction([new Dog(), new Fish()]);
|
类的成员修饰符
在 TypeScript 中,类的属性和方法支持三种修饰符:
public、private、protected
- public 修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是 public 的
- private 修饰的是仅在同一类中可见、私有的属性或方法
- protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法
- public 是默认的修饰符,也是可以直接访问的
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 28 29 30 31 32 33 34 35 36 37
| class Person { name: string = "xxx"; } let p = new Person(); console.log(p.name);
class Person2 { private name: string = "xxx"; getname() { return this.name; } } let p2 = new Person2();
console.log(p2.getname());
class Person1 { protected name1: string = "xxx"; getname() { return this.name1; } } let p1 = new Person1();
console.log(p1.getname());
class Stu extends Person1 { get() { return this.name1; } } let s1 = new Stu(); console.log(s1.get());
|
只读属性 readonly
如果有一个属性我们不希望外界可以任意的修改,只希望确定值后直接使用,那么可以使用 readonly
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Person { readonly name: string = "yy"; age: number = 20; readonly friend?: Person; constructor(name: string, friend?: Person) { this.name = name; this.friend = friend; } } let p = new Person("xxxx"); console.log(p.name);
let p2 = new Person("nnn", new Person("kkk"));
console.log(p2.friend); if (p2.friend) { p2.friend.age = 30; console.log(p2.friend.age); } export {};
|
getters/setters
前面一些私有属性我们是不能直接访问的,或者某些属性我们想要监听它的获取(getter)和设置(setter)的过程, 这个时候我们可以使用存取器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Person { private _name: string = "";
set name(newName) { this._name = newName; } get name() { return this._name; } } let p = new Person(); p.name = "ccc"; console.log(p.name);
export {};
|
静态成员
前面我们在类中定义的成员和方法都属于对象级别的, 在开发中, 我们有时候也需要定义类级别的成员和方法
在 TypeScript 中通过关键字 static 来定义:
1 2 3 4 5 6 7 8 9 10 11
| class Person { static time: string = "10:10";
static get() { return this.time; } } console.log(Person.time); console.log(Person.get());
export {};
|
抽象类 abstract
继承是多态使用的前提。
所以在定义很多通用的调用接口时, 我们通常会让调用者传入父类,通过多态来实现更加灵活的调用方式
但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,我们可以定义为抽象方法
什么是抽象方法?
- 在 TypeScript 中没有具体实现的方法(没有方法体),就是抽象方法。
- 抽象方法,必须存在于抽象类中
- 抽象类是使用 abstract 声明的类
- 抽象类有如下的特点:
- 抽象类是不能被实例的话(也就是不能通过 new 创建)
- 抽象方法必须被子类实现,否则该类必须是一个抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function makeArea(shape: Shape) { return shape.getArea(); }
abstract class Shape { abstract getArea(); }
class Jx extends Shape { width: number; length: number;
constructor(width: number, length: number) { super(); this.width = width; this.length = length; } getArea() { return this.width * this.length; } } let jx = new Jx(10, 20); console.log(makeArea(jx));
|
类的类型
类本身也是可以作为一种数据类型的
1 2 3 4 5 6 7 8 9 10 11 12
| class Person { name: string;
eat() {} }
let p = new Person();
let p1: Person = { name: "xxx", eat: function () {}, };
|