在早期的JavaScript开发中( ES5)我们需要通过函数和原型链来实现类和继承,从ES6开始,引入了class关键字,可以更加方便的定义和使用类。
TypeScript作为JavaScript的超集,也是支持使用class关键字的,并且还可以对类的属性和方法等进行静态类型检测。
实际上在JavaScript的开发过程中,我们更加习惯于函数式编程:
比如React开发中,目前更多使用的函数组件以及结合Hook的开发模式;
比如在Vue3开发中,目前也更加推崇使用 Composition API;
但是在封装某些业务的时候,类具有更强大封装性,所以我们也需要掌握它们。
类的定义我们通常会使用class关键字:
在面向对象的世界里,任何事物都可以使用类的结构来描述;
类中包含特有的属性和方法;
例如我们使用class关键字来定义一个Person类 ;
类中如果类型没有声明,那么它们默认是any的;
我们也可以给属性设置初始化值;
在默认的strictPropertyInitialization模式下面我们的属性是必须初始化的
class Person {
name: string = ""
age: number = 0
}
如果没有初始化,那么编译时就会报错;
如果我们在strictPropertyInitialization模式下确实不希望给属性初始化,可以使用 name!: string语法;
class Person {
name!: string
age!: number
}
类可以有自己的构造函数constructor,当我们通过new关键字创建一个实例时,构造函数会被调用;
构造函数不需要返回任何值,默认返回当前创建出来的实例;
我们也可以使用构造函数constructor对属性进行初始化
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
类中可以有自己的函数,定义的函数称之为方法;
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
eating() {
console.log("is eating")
}
running() {
console.log("is running")
}
}
// 测试
const p = new Person("chenyq", 18)
console.log(p.name)
console.log(p.age)
p.eating()
p.running()
面向对象的其中一大特性就是继承,继承不仅仅可以减少我们的代码量,也是多态的使用前提。
我们使用extends关键字
来实现继承,子类中使用super来访问父类。
我们来看一下Student类和Teacher类继承自Person类:
Student类和Teacher类可以有自己的属性和方法,并且会继承Person的属性和方法;
class Person {
name: string = ""
age: number = 0
eating() {
console.log("eating")
}
}
class Student extends Person {
score: number = 0
studying() {
console.log("studying")
}
}
class Teacher extends Person {
title: string = ""
teaching() {
console.log("teaching")
}
}
// 测试
const stu = new Student()
stu.name = "chenyq"
stu.age = 18
console.log(stu.name)
console.log(stu.age)
stu.eating()
stu.studying()
const tea = new Teacher()
tea.name = "kaisa"
tea.age = 20
console.log(tea.name)
console.log(tea.age)
tea.eating()
tea.teaching()
但是目前这种做法我们还需要在实例中对name, age属性赋值, 这种方式会很麻烦, 我们希望创建对象实例的时候就可以进行初始化
在构造函数中,我们可以通过super()方法, 来调用父类的构造方法,对父类中的属性进行初始化;
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 {
score: number = 0
constructor(name: string, age: number, score: number) {
// 调用父类构造器
super(name, age)
this.score = score
}
studying() {
console.log("studying")
}
}
// 测试
const stu = new Student("chenyq", 18, 110)
console.log(stu.name)
console.log(stu.age)
console.log(stu.score)
stu.eating()
stu.studying()
目前我们调用的eating方法, 是由父类Person继承的, 当我们Student子类对父类方法不满意时, 我们可以重写
class Student extends Person {
score: number = 0
constructor(name: string, age: number, score: number) {
// 调用父类构造器
super(name, age)
this.score = score
}
// 重写父类方法
eating() {
console.log("student eating")
}
studying() {
console.log("studying")
}
}
如果我们重写子类eating方法时, 又想让父类的eating方法也执行一次, 可以通过super调用
class Student extends Person {
score: number = 0
constructor(name: string, age: number, score: number) {
// 调用父类构造器
super(name, age)
this.score = score
}
// 重写父类方法
eating() {
// 子类中是由super, 再让父类的方法也执行一次
super.eating()
// 重写的部分
console.log("student eating")
}
studying() {
console.log("studying")
}
}
在TypeScript中,类的属性和方法支持三种修饰符:public、 private、 protected
public修饰符
: 是在任何地方可见、公有的属性或方法,默认编写的属性就是public的;例如下面代码中, 属性默认的修饰符是public, 因此可在任何地方访问
class Person {
name: string = ""
}
// 测试
const p = new Person()
// 可以在任何地方访问类中的额name属性
p.name = "aaa"
private修饰符
: 是仅在同一类中可见、私有的属性或方法;private修饰的属性或方法, 只能在同一类中访问, 外部访问就会报错
class Person {
private name: string = "a"
getName() {
return this.name
}
}
// 测试
const p = new Person()
// p.name // 外部无法访问
console.log(p.getName())
protected修饰符
: 是仅在类自身及子类中可见、受保护的属性或方法;protected修饰符除自身类中可以访问之外, 子类中也可以访问, 外部同样无法访问到
class Person {
protected name: string = "a"
}
class Student extends Person {
getName() {
// 子类中也可以访问到protected
return this.name
}
}
// 测试
const stu = new Student()
console.log(stu.getName())
readonly准确的说也是一个修饰符, 使用readonly修饰符修饰的属性是一个只读属性
如果有一个属性我们不希望外界可以任意的修改,只希望确定值后直接使用,那么可以使用readonly修饰符:
class Person {
readonly name: string = "aaa"
}
// 测试
const p = new Person()
console.log(p.name)
// p.name = "bbb" // 只能读, 不能修改
但是只读属性是可以在constructor构造器中赋值的, 赋值之后同样不可修改, 演示如下
class Person {
readonly name: string
constructor(name: string) {
this.name = name
}
}
// 测试
const p = new Person("abc")
console.log(p.name)
在前面一些私有属性我们是不能直接访问的,或者某些属性我们想要监听它的获取(getter)和设置(setter)的过程, 这个时候我们可以使用访问器。
访问器的定义是类似一个函数的形式, 而调用又和一般函数不一样, 类似于属性的方式
class Person {
private _name: string
constructor(name: string) {
this._name = name
}
// 访问器 setter/getter定义方式
set name(newName) {
this._name = newName
}
get name() {
return this._name
}
}
// 测试
const p = new Person("abc")
// 访问器的访问方式
p.name = "aaa"
console.log(p.name)
面我们在类中定义成员的属性和方法都属于对象实例的, 但是在开发中, 我们有时候也需要定义类级别的成员和方法(也就是类属性、类方法)。
在TypeScript中通过关键字static
来定义:
class Student {
// 定义类的属性
static time: string = "20:00"
// 定义类方法
static studying() {
console.log("去上课学习")
}
}
// 测试
console.log(Student.time)
Student.studying()