TypeScript面向对象

类 class

JS从ES6开始,引入了class关键字,可以更加方便的定义和使用类

TS作为JS的超集,支持使用class关键字,可以对类的属性和方法等进行静态类型检测

使用例子

class Person {
  name: string
  age: number
  // 构造函数
  constructor(name: string age: number){
    this.name = name
    this.age = age 
  }

  running(){
    console.log(this.name + ' running')
  }
  eating(){
    console.log(this.name + ' eating')
  }
}
// 创建实例
const kun = new Person('ikun',18)
kun.running()

类的继承

  • 面向对象其中一大特征就为继承,可减小代码量
  • 使用 extends 关键字来继承,子类使用 super 访问父类
// 继承上面的 Person 类

class Student extends Person {
  id: number

  constructor(name: string, age: number, id: number){
    super(name,age)
    this.id = id
  }

  running() {
    super.running()
    console.log(this.name + ' student running')
  }
  
  studying() {
    console.log(this.name + ' studying')
  }
  
}
const zs = new Student('zs',18,1)
// zs running
// zs student running
zs.running()
// zs studying
zs.studying()

Student可以有自己的属性和方法,并且会继承Person的属性和方法
在构造函数中,我们可以通过super来调用父类的构造方法,对父类中的属性进行初始化

类的成员修饰符

修饰符

在TS中类的属性和方法支持publicprivateprotected三种修饰符

  • public: 默认类型,公有属性和方法,任何地方可见
  • private:私有类型,属性和方法仅同一类可见
  • protected:保护类型,属性和方法仅同一类及子类可见
class Animal {
  // 约定私有 _+属性名
  private _name: string

  constructor(_name: string){
    this._name = _name
  }
  
}

const cat = new Animal('kk')
// error 错误,_name是私有的
console.log(cat._name)

只读属性readonly

如果属性我们不希望外界可以任意的修改,只希望确定值后直接使用,可以使用readonly

class Animal {
  readonly name: string
  constructor(name: string){
    this.name = name
  }
}

const cat = new Animal('kk')
// error 错误 name 是只读属性
cat.name = 'gg' 

可选属性(Optional Properties)

interface IPerson {
  name: string
  // 表示 age属性是可选的
  // 类型为:number|undefined
  age?: number
}

const p: IPerson={
  name: 'kun'  
}
  

getters/setters

因为被private修饰属性和方法的不能直接访问,使用存取器截取对对象成员的访问

class Person {
  private _name: string
  
  set name(newName){
    this._name = newName
  }
  get name(newName){
    return this._name
  }

  constructor(name: string){
    this._name = name
  }
}

const p = new Person('kk')
p.name = 'gg'
console.log(p.name) // gg

参数属性

TS 提供了特殊的语法,可以把一个构造函数参数转成一个同名同值的类属性

class Person {
  constructor(public name: string, private _age: number){}
  set age(newAge){
    this._age = newAge
  }
  get age(){
    return this._age
  }
}

const kun = new Person('ikun',18)
kun.age = 20
console.log(kun.age) // 20

构造函数参数前添加一个可见性修饰符 public private protected 或者 readonly 来创建参数属性,最后这些类属性字段也会得到这些修饰符

抽象类abstract

继承是多态使用的前提,调用接口时传入父类,通过多态实现更加灵活的调用方法
所以父类不需要对某些方法进行具体的实现,可以定义抽象方法
抽象方法:

  1. 存在抽象类中
  2. 抽象类使用 abstract 声明的类
// 抽象类
abstract class Shape {
  abstract getArea(): number
}
// 子类继承实现
class Circle extends Shape {
  constructor(private r: number ){
    super()
  }
  getArea(){
    return this.r * this.r * 3.14
  }
}

const circle = new Circle(3)
console.log(circle.getArea()) // 28.26

特点

  • 抽象类不能被实例化
  • 抽象类除了抽象方法,还可以定义含有实现体的方法
  • 抽象方法,一定是在抽象类中
  • 抽象方法必须被子类实现

类的数据类型

类本身也可以当作数据类型

class Person {
  name: string
  age: number
  // 构造函数
  constructor(name: string age: number){
    this.name = name
    this.age = age 
  }
}

// Person 当作类型
const kun: Person = new Person('ikun',18)
const gg: Person ={
  name: 'kunkun'
  age: 11
}

索引签名(Index Signatures)

  1. 有的时候,你不能提前知道一个类型里的所有属性的名字,但是你知道这些值的特征
  2. 这种情况,你就可以用一个索引签名 (index signature) 来描述可能的值的类型
interface Icollertion{
  // 可以通过number 类型拿到值的变量/常量
  [index:number]: any
  // 拥有length属性的变量/常量
  length: number
}
// 实现一个可以遍历的函数
function foo(collection: Icollertion){
  for(let i = 0, i<= collection.length;i++){
    console.log(collection[i])
  }
}
const arr: string[] = ['aaa','bbb','ccc']
const tuple: [string,number,number] = ['ddd',1,3]
// arr 可以通过number拿着值也有length属性 arr[0], arr.length
foo(arr)
// tuple 同理
foo(tuple)

一个索引签名的属性类型必须是 string 或者是 number

接口

继承

接口和类一样是可以进行继承的,也是使用extends关键字
不同的是接口可以实现多继承

interface IRun {
  running: ()=>void
}
interface ISwim {
  swimming: () => void
}

interface Person extends IRun,ISwim{
  name :string
}

实现

使用 implements 来实现

与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约

interface IRun {
  name: string
  running: ()=>void
}

class Person implements {
  name: string
  constructor(name: string){
    this.name = name
  }
  running(){
    console.log('running')
  }
}

抽象类和接口的区别

抽象类在很大程度上和接口会有点类似:都可以在其中定义一个方法,让子类或实现类来实现对应的方法。

抽象类和接口么区别:

  • 抽象类是事物的抽象,抽象类用来捕捉子类的通用特性,接口通常是一些行为的描述
  • 抽象类通常用于一系列关系紧密的类之间,接口只是用来描述一个类应该具有什么行为
  • 接口可以被多层实现,而抽象类只能单一继承
  • 抽象类中可以有实现体,接口中只能有函数的声明

通常我们会这样来描述类和抽象类、接口之间的关系:

  • 抽象类是对事物的抽象,表达的是 is a 的关系。猫是一种动物(动物就可以定义成一个抽象类)
  • 接口是对行为的抽象,表达的是 has a 的关系。猫拥有跑(可以定义一个单独的接口)、爬树(可以定义一个单独的接口)
    的行为。

严格的字面量赋值检测

  • 每个对象字面量最初都被认为是新鲜的(fresh)
  • 当一个新的对象字面量分配给一个变量或传递给一个非空目标类型的参数时,对象字面量指定目标类型中不存在的属性是错误的
  • 当类型断言或对象字面量的类型扩大时,新鲜度会消失

简单理解,对象再第一次创建/实例化时,类型会严格检查,不符合会报错
对象已经存在再被赋值时,TS不进行类型检测了

interface Iinfo {
  name: string
  age: number
}

// error 错误不符合 Iinfo 规定的
const person1: Iinfo = {
  name: 'kunkun'
}

const person2 = {
  name: 'kunkun'
}
// 不会报错,person2不是新鲜的。即不是第一次定义
const person3: Iinfo = person2

枚举类型

  • 枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型
  • 枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型

关键字 enum

// 定义
enum Position {
  LFET,
  RIGHT,
  TOP,
  BOTTOM
}
// 通过枚举的属性来访问枚举成员,和枚举的名字来访问枚举类型
function foo(position: Position){
  switch(position){
    case: position.LFET
      console.log('左边')
      break
    case: position.RIGHT
      console.log('右边')
      break
    // ...
  }
}

枚举类型默认是有值的

  1. 上面的 LFET 默认 为 0RIGHT1 自增长
  2. 当我们不在乎成员的值的时候,这种自增长的行为是很有用处的,但是要注意每个枚举成员的值都是不同的
  3. 如果自定义值 就如 LEFT = 1,后面的RIGHT2
  4. 也可以赋值字符串,字符串枚举没有自增长的行为

参考资料

TS中文网

coderwhy前端课程