TS 面向对象
认识类的使用
在早期的 JavaScript 开发中(ES5)我们需要通过函数和原型链来实现类和继承,从 ES6 开始,引入了class关键字
,可以更加方 便的定义和使用类
TypeScript 作为 JavaScript 的超集,也是支持使用 class 关键字的,并且还可以对类的属性和方法等进行静态类型检测
实际上在 JavaScript 的开发过程中,我们更加习惯于函数式编程:
- 比如 React 开发中,目前更多使用的函数组件以及结合 Hook 的开发模式;
- 比如在 Vue3 开发中,目前也更加推崇使用 Composition API;
类的定义我们通常会使用 class 关键字:
- 在面向对象的世界里,任何事物都可以使用类的结构来描述;
- 类中包含特有的
属性和方法
;
类的定义
class Person {
// 成员属性: 声明成员属性
name: string = ''
age: number = 0
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
// 实例对象: instance
const p1 = new Person('sj', 19)
console.log(p1) // Person { name: 'sj', age: 19 }
类的继承
我们使用 extends
关键字来实现继承,子类中使用 super
来访问父类。
我们来看一下 Student
类继承自 Person:
- Student 类可以有自己的属性和方法,并且会继承 Person 的属性和方法;
- 在构造函数中,我们可以通过 super 来调用父类的构造方法,对父类中的属性进行初始化;
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
class Student extends Person {
sno: number
constructor(name: string, age: number, sno: number) {
super(name, age)
this.sno = sno
}
studying() {
console.log(this.sno + 'studying')
}
}
const p = new Person('sj', 19)
console.log(p) // Person { name: 'sj', age: 19 }
const p1 = new Student('sj', 19, 99)
console.log(p1) // Student { name: 'sj', age: 19, sno: 99 }
p1.studying()
类的成员修饰符
在 TypeScript 中,类的属性和方法支持三种修饰符: public、private、protected
public
修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是 public 的;private
修饰的是仅在同一类中可见、私有的属性或方法;protected
修饰的是仅在类自身及子类中可见、受保护的属性或方法;
public 是默认的修饰符,也是可以直接访问的,我们这里来演示一下 protected 和 private。
class Person {
// 修饰的是仅在类自身及子类中可见、受保护的属性或方法;
protected name: string
// 修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是public的
public age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
// e 修饰的是仅在同一类中可见、私有的属性或方法;
private eating() {
console.log('吃饭了', this.name)
}
}
const p = new Person('sj', 19)
console.log(p) // Person { name: 'sj', age: 19 }
class Student extends Person {
constructor(name: string, age: number) {
super(name, age)
}
}
const p1 = new Student('XQ', 19)
console.log(p1) // Student { name: 'XQ', age: 19 }
只读属性 readonly
class Person {
readonly name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
// 类和实例的关系
const p = new Person('sj', 19)
console.log(p.name, p.age)
// 只读属性不能写入
p.name = 'XQ' // 报错
getters/setters
class Person {
// 私有属性(内部): 私有属性属性前面加 _
private _name: string
constructor(name: string) {
this._name = name
}
running() {
console.log('running', this._name)
}
// getters/setters: 对属性的访问进行拦截操作
set name(newValue: string) {
this._name = newValue
}
get name() {
return this._name
}
}
// 类和实例的关系
const p = new Person('sj')
console.log(p.name) // sj
p.running() // running sj
// setters
p.name = 'XQ'
// getters
console.log(p.name) //XQ
export {}
参数属性
TypeScript 提供了特殊的语法,可以把一个构造函数参数转成一个同名同值的类属性。
- 这些就被称为参数属性(parameter properties);
- 你可以通过在构造函数参数前
添加一个可见性修饰符 public private protected 或者 readonly 来创建参数属性,最后这些类 属性字段也会得到这些修饰符;
class Person {
// 语法糖
constructor(public name: string, private _age: number) {
this.name = name
this._age = _age
}
running() {
console.log('running', this.name) // running sj
}
set age(newValue: number) {
this._age = newValue
}
get age() {
return this._age
}
}
const p = new Person('sj', 19)
console.log(p) // Person { name: 'sj', age: 19 }
p.age = 20 // 20
console.log(p.age)
p.running()
export {}
抽象类 abstract
我们知道,继承是多态使用的前提。
- 所以在定义很多通用的调用接口时, 我们通常会让调用者传入父类,通过多态来实现更加灵活的调用方式。
- 但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,我们可以定义为抽象方法。
什么是 抽象方法? 在 TypeScript 中没有具体实现的方法(没有方法体),就是抽象方法。
- 抽象方法,必须存在于抽象类中;
- 抽象类是使用 abstract 声明的类;
抽象类有如下的特点:
- 抽象类是不能被实例的话(也就是不能通过 new 创建)
- 抽象类可以包含抽象方法,也可以包含有实现体的方法;
- 有抽象方法的类,必须是一个抽象类;
- 抽象方法必须被子类实现,否则该类必须是一个抽象类;
abstract class shape {
// getArea 方法只有声明没有实现体
// 实现体让子类自己实现
// 可以将方法定义为抽象方法: 在方法前面添加 abstract
// 抽象方法必须出现在抽象对象中,所以类也加 abstract
abstract getArea(): number
}
class Rectangle extends shape {
constructor(public width: number, public height: number) {
super()
}
getArea() {
return this.width * this.height
}
}
class Circle extends shape {
constructor(public radius: number) {
super()
}
getArea() {
return this.radius ** 2 * Math.PI
}
}
// 通用函数
function calcArea(shape: shape) {
// 调用子类的方法
const res = shape.getArea()
console.log(res)
return res
}
calcArea(new Rectangle(10, 10)) // 314.1592653589793
calcArea(new Circle(10)) // 314.1592653589793
// calcArea(new shape()) 抽象类不能有实例
类的类型
class Person {}
/**
* 类的作用:
* 1.可以创建类对应的实例对象
* 2.类本身可以作为这个实例的类型
* 3.类可以当做有一个构造签名的函数
*/
const name: string = 'aaa'
const p: Person = new Person()
function PrintPerson(p: Person) {}
function factory(ctor: new () => void) {}
factory(Person)
对象类型的修饰符
对象类型中的每个属性可以说明它的类型、属性是否可选、属性是否只读等信息。
可选属性(Optional Properties):
- 我们可以在属性名
后面加一个 ? 标记
表示这个属性是可选的;
只读属性(Readonly Properties)
- 在 TypeScript 中,属性
可以被标记为 readonly
,这不会改变任何运行时的行为; - 但在类型检查的时候,一个标记为 readonly 的属性是不能被写入的
// 定义对象类型
type IPerson = {
// 属性 ? :可选类型
name?: string
// readonly: 只读属性
readonly age: number
}
interface ILike {
like: string
}
const p: IPerson = {
name: 'sj',
age: 19,
}
p.age = 20 // 报错(只读)
console.log(p)
索引签名
// 1.索引签名的理解
interface IInfoType {
// 所以签名: 通过字符串获取一个值,也是字符串
[key: string]: string
// [index: string]: number
}
function getInfo(): IInfoType {
const name: any = 'sj'
return name
}
const info = getInfo()
console.log(info) // sj
// 2.索引签名的案例
interface ICollection {
[index: number]: string
length: number
}
function printCollection(collection: ICollection) {
for (let i = 0; i < collection.length; i++) {
const item = collection[i]
console.log(item)
console.log(item.length)
}
}
const names = ['aaa', 'bbb', 'ccc'] // 索引 0 1 2 => number
const tuple: [string, string] = ['sj', '19'] // 索引 0 1 => number
printCollection(names)
printCollection(tuple)
export {}
接口继承
interface IPerson {
name: string
age: number
}
/**
* 继承属性:
* 1.减少了相同代码的重复编写
* 2.如果使用第三方库,给我们自定义了一些属性
* > 自定义一个接口,同时你希望拥有第三方中的类型
* > 可以通过继承来完成你需要的第三方属性
*/
interface ILike extends IPerson {
like: string[]
}
const sj: ILike = {
name: 'sj',
age: 19,
like: ['抽烟', '喝酒', '打麻将'],
}
console.log(sj) // { name: 'sj', age: 19, like: [ '刷抖音', '抽烟', '打麻将' ] }
接口的实现
interface ISj {
name: string
age: number
like: () => void
}
const sj: ISj = {
name: 'sj',
age: 19,
like: function () {
console.log('Hello World')
},
}
console.log(sj) // { name: 'sj', age: 19, like: [Function: like] }
// 作用: 接口被类实现
class Person implements ISj {
name: string = 'XQ'
age: number = 19
like: () => void = function () {
console.log('Hello World')
}
}
const ISj2 = new Person()
console.log(ISj2.name, ISj2.age) // XQ 19
ISj2.like() // Hello World
严格字面量赋值检查
错误:
interface IPerson {
name: string
age: number
}
const p: IPerson = {
name: 'sj',
age: 19,
like: ['抽烟', '喝酒', '打麻将'], // 报错
}
function printInfo(info: IPerson) {
console.log(info.name, info.age, info.like) // 报错
}
printInfo({ name: 'sj', age: 19, like: ['抽烟', '喝酒', '打麻将'] }) // 报错
正确:
interface IPerson {
name: string
age: number
}
const p = {
name: 'sj',
age: 19,
like: ['抽烟', '喝酒', '打麻将'],
}
// 解释现象:
// 第一次场景对象字面量,称为fresh(新鲜)
// 对象新鲜的字面量严格检查,后面不在进行严格检查
const info: IPerson = p
console.log(info) // { name: 'sj', age: 19, like: [ '抽烟', '喝酒', '打麻将' ] }
function printInfo(info: any) {
console.log(info.name, info.age, info.like) // sj 19 [ '抽烟', '喝酒', '打麻将' ]
}
const sj = { name: 'sj', age: 19, like: ['抽烟', '喝酒', '打麻将'] }
printInfo(sj)
抽象类和接口的区别(了解)
抽象类在很大程度上和接口会有点类似:都可以在其中定义一个方法,让子类或实现类来实现对应的方法
那么抽象类和接口有什么区别呢?
- 抽象类是事物的抽象,抽象类用来捕捉子类的通用特性,接口通常是一些行为的描述;
- 抽象类通常用于一系列关系紧密的类之间,接口只是用来描述一个类应该具有什么行为;
- 接口可以被多层实现,而抽象类只能单一继承;
- 抽象类中可以有实现体,接口中只能有函数的声明;
通常我们会这样来描述类和抽象类、接口之间的关系:
- 抽象类是对事物的抽象,表达的是 is a 的关系。猫是一种动物(动物就可以定义成一个抽象类)
- 接口是对行为的抽象,表达的是 has a 的关系。猫拥有跑(可以定义一个单独的接口)、爬树(可以定义一个单独的接口) 的行为。
TypeScript 枚举类型
枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型;
枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型.
// 定义一个枚举类型
enum Direction {
UP,
DOWN,
LEFT,
RIGHT,
}
const d1: Direction = Direction.LEFT
console.log(d1) // 2
function turnDirection(direction: Direction) {
switch (direction) {
case Direction.UP:
console.log('向上移动')
break
case Direction.DOWN:
console.log('向下移动')
break
case Direction.LEFT:
console.log('向左移动')
break
case Direction.RIGHT:
console.log('向右移动')
break
}
}
// 监听键盘点击
turnDirection(Direction.LEFT) // 向左移动
export {}
枚举类型的值
枚举类型默认是有值的,比如上面的枚举,默认值是这样的:
当然,我们也可以给枚举其他值:
- 这个时候会从 100 进行递增;
// 定义一个枚举类型
// 默认
enum Direction {
TOP = 0,
BOTTOM = 1,
LEFT = 2,
RIGHT = 3,
}
// 赋值 后面会自动加 100
enum Direction {
TOP = 100,
BOTTOM,
LEFT,
RIGHT,
}
// 赋值
enum Direction {
TOP = 'TOP',
BOTTOM = 'BOTTOM',
LEFT = 'LEFT',
RIGHT = 'RIGHT',
}
const d1: Direction = Direction.TOP
export {}