一、类的概述
在早期的JavaScript
开发中(ES5
)需要通过函数和原型链来实现类和继承。
从ES6
开始,引入了class
关键字,可以更加方便的定义和使用类。
TypeScript
是JavaScript
的超集,也支持使用class
关键字,还支持对类的属性和方法等进行静态类型检测。
**虽然在JavaScript
的开发过程中,更加习惯于函数式编程,而不是面向对象编程: **
React
开发中,目前更多使用的函数组件以及结合Hook
的开发模式- 比如在
Vue3
开发中,目前也更加推崇使用Composition API
但是在封装某些业务的时候,类也具有更强大封装性。
类的定义我们通常会使用class关键字:
- 在面向对象的编程中,任何事物都可以使用类的结构来描述
- 类中可以包含一些自己特有的属性和方法
- 类也很好的诠释了面向对象的三大特性,继承、封装、多态
二、类的定义
2.1 使用class
关键字来定义一个类;
class Teacher {
}
2.2 在类的内部声明类的属性以及对应的类型
class Teacher {
name: string
age: number
}
如果类型没有声明,那么它们默认是any的;
类的属性可以设置初始化值:
class Teacher {
name: string = "zs"
age: number = "18"
}
在默认的strictPropertyInitialization
模式下类的属性是必须初始化的,否则编译时就会报错;
如果在strictPropertyInitialization
模式下确实不希望给属性初始化,可以使用name!: string
语法。
class Teacher {
name!: string
age!: number
}
2.3 声明类的构造函数
class Teacher {
name: string = "zs"
age: number = "18"
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
类中的构造函数constructor,当通过new关键字创建一个类的实例时,构造函数会被调用
构造函数不需要返回任何值,默认返回当前创建出来的实例
2.3 定义类中的方法
class Teacher {
name: string = "zs"
age: number = "18"
constructor(name: string, age: number) {
this.name = name
this.age = age
}
eating() {
console.log(this.name + " eating")
}
}
类中可以定义一些自己的函数,定义的函数称之为方法
2.4 类的基本使用
const p = new Teacher("ls", 25)
console.log(p.name, p.age)
p.eating()
三、类的继承
面向对象的其中一大特性就是继承,继承不仅仅可以减少我们的代码量,也是多态的使用前提。
TypeScript
中使用extends
关键字来实现继承,子类中使用super
来访问父类中的方法。
注意点:
TypeScript
中的类只支持单继承,意为一个类只能继承一个父类,而不能继承多个。
3.1 继承的基本写法
// 定义父类Person
class Person {
name: string = ""
age: number = 0
eating() {
console.log("eating")
}
}
// 定义子类Teacher
class Teacher extends Person {
title: string = ""
constructor(name: string, age: number, title: string) {
// super调用父类的构造器,对父类中的属性进行初始化
super(name, age)
this.title = title
}
teaching() {
console.log("teaching")
}
}
// 使用Teacher
const theacher = new Teacher('zs', 25, '金牌教师')
console.log(theacher.name, theacher.age, theacher.title)
theacher.eating()
Student
类可以有自己的属性和方法,并且会继承Teacher
的属性和方法;在构造函数中,我们可以通过
super
来调用父类的构造方法,对父类中的属性进行初始化;
3.2 方法重写
在上面的代码关系中,theacher.eating()
的结果是eating
,完全来自于父类中的eating()
方法。
此时子类可以自己重新实现属于自己的eating()
方法,这就是函数重写。
// 定义子类Teacher
class Teacher extends Person {
title: string = ""
constructor(name: string, age: number, title: string) {
// super调用父类的构造器,对父类中的属性进行初始化
super(name, age)
this.title = title
}
teaching() {
console.log("teaching")
}
// 重写父类中的eating方法
eating() {
// 有需要的话,可以使用super让父类的eating方法也执行一次
super.eating()
// 重写的部分
console.log("teacher eating")
}
}
四、类的多态
class Animal {
action() {
console.log("animal action")
}
}
class Dog extends Animal {
action() {
console.log("dog running!!!")
}
}
class Fish extends Animal {
action() {
console.log("fish swimming")
}
}
function makeActions(animals: Animal[]) {
animals.forEach(animal => {
animal.action()
})
}
// Dog和Fish都继承于Animal,本质也是一种Animal
// 根据多态,父类引用可以指向子类的对象,能接收父类作为参数的,也能够接收子类作为参数
// 参数中的new Dog()的返回值是一个Animal animal,相当于Animal animal = new Dog()
// 但是执行action的时候,执行的是具体的那个new出来的对象中的action
makeActions([new Dog(), new Fish(), new Animal()])
多态的目的是为了写出更加具备通用性的代码
多态的重要特点就是父类引用可以指向子类的对象,子类对象可以代替父类出现的地方
五、类中的成员的修饰符
在TypeScript
中,类的属性和方法支持三种修饰符: public
、private
、protected
public
修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是public
的
class Person {
public name: string = ""
}
不写任何修饰符,默认属性和方法的修饰符就是public
private
修饰的是仅在同一类中可见、私有的属性或方法
class Teacher {
private name: string = "" // 定义一个私有顺序name,只有在本类中才可以被访问
// 封装了两个对外开放的方法, 通过这两个方法来访问name
getName() {
return this.name
}
setName(newName) {
this.name = newName
}
}
protected
修饰的是仅在类自身及子类中可见、受保护的属性或方法
class Person {
protected name: string = "123"
}
class Teacher extends Person {
getName() {
return this.name
}
}
被protected修饰的属性和方法,只有在类内部和子类中可以访问
六、类中的只读属性
如果有一个属性不希望外界可以任意的修改,只希望确定值后直接使用,就可以使用readonly
修饰类的属性。
readonly
准确的说也是一个修饰符, 使用readonly
修饰符修饰的属性是一个只读属性。
class Teacher {
readonly name: string
constructor(name: string) {
this.name = name
}
}
// new 对象的时候就确定了name的值后,就不可以在new 出来的实例当中再去修改了
const teacher = new Teacher("zs")
console.log(teacher.name)
// teacher.name = 'ls' 此时想去这么写代码的话teacher.name就会报错标红
注意点:
只读属性是可以在构造器中赋值, 赋值之后就不可以修改
属性本身不能进行修改, 但是如果它是对象类型, 对象中的属性是可以修改,只要对象的内存地址不改变即可
七、类中的getters/setters
当类中的一些私有属性外界是不能直接访问的,想要监听或者某些属性的获取(getter
)和设置(setter
)的过程。
这个时候就可以使用访问器的写法。
class Teacher {
private _name: string
constructor(name: string) {
this._name = name
}
// 访问器的写法
// setter
set name(newName) {
this._name = newName
}
// getter
get name() {
return this._name
}
}
// 访问器调用方式
const p = new Teacher("zs")
p.name = "ls" // 虽然访问器叫做name(),但是调用的时候还是像调用属性一样去调用
console.log(p.name)
相比于直接写get/set方法的方式去暴露私有属性,更加推荐写访问器的方式去露私有属性
八、类中的静态成员
一般来说,在类中定义的成员和方法都属于对象级别的,它们属于每一个该类的对象实例。
在开发中, 有时候也需要定义类级别的成员和方法。 意思就是该成员变量属于类,而不属于该类的对象实例。
在TypeScript
中通过关键字static
来定义一个静态成员。
class Student {
static time: string = "09:00"
static attendClass() {
console.log("去学习typescript~")
}
}
console.log(Student.time) // 直接通过类名去调用静态成员变量
Student.attendClass() // 直接通过类名去调用静态成员方法
静态成员的最大特点就是属于类而不属于类的实例
静态成员需要用类名直接去调用
九、抽象类
9.1 抽象方法的概念
继承是多态使用的前提。 所以在定义通用接口时, 通常会把调用者传入父类,通过多态实现更加灵活的调用方式。
但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,我们可以定义为抽象方法。
在TypeScript
中没有具体实现的方法(没有方法体),就是抽象方法。
9.2 抽象类的概念
具有抽象方法的类就是抽象类。
9.3 抽象类的特点
- 抽象方法,必须存在于抽象类中
- 抽象类使用
abstract
声明 - 抽象类是不能被实例化(即不能通过
new
创建对象) - 抽象方法必须被子类实现,否则该子类必须是一个抽象类, 如果是一个抽象类就可以不实现;
9.4 代码示例
// 定义一个形状的抽象类
abstract class Shape {
abstract getArea(): number
}
// 定义正方形类(继承shape类并且实现shape类中的抽象方法)
class Square extends Shape {
private width: number
private height: number
constructor(width: number, height: number) {
super()
this.width = width
this.height = height
}
getArea() {
return this.width * this.height
}
}
// 定义圆形形类(继承shape类并且实现shape类中的抽象方法)
class Circle extends Shape {
private r: number
constructor(r: number) {
super()
this.r = r
}
getArea() {
return this.r * this.r * 3.14
}
}
// 定义计算面积的类,接收一个Shape类型的参数,所以这个参数应该传入Shape类的实现类的实例
function makeArea(shape: Shape) {
return shape.getArea()
}
// 测试
const rectangle = new Rectangle(20, 30)
const circle = new Circle(10)
// 根据传入的形状实例去调用对应的getArea方法
console.log(makeArea(rectangle)) // 20*30
console.log(makeArea(circle)) // 10*10*3.14
十、类的类型
在TypeScript
中,类本身也可以作为一种数据类型。
10.1 类的实例的类型就是类本身
class Teacher {
name: string = "zs"
sleep() { console.log("sleeping...") }
}
const teacher = new Teacher()
此时的teacher变量(对象),它的类型就是Teacher
10.2 直接定义一个变量,指定它的类型为某个类
const teacher: Teacher = {
name: "ls"
sleep() { console.log("sleeping...") }
}
注意点就是teacher变量的属性列表要和Teacher类保持一致
10.3 类类型在开发中的使用
function printPerson(t: Teacher) {
console.log(t.name)
}
printPerson(new Teacher())
printPerson({name: "jack", eating: function() {console.log("sleeping...")}})
一般类类型都是作为函数的参数进行使用