#

与ES6一样,Typescript也是用class来定义类,不同于ES5以前用函数和原型来实现继承

# 举个🌰

  class Person {
    name: string
    age: number
    sex: string
    // 构造函数
    constructor(name: string = '名字', age: number = 0, sex: string = '未知') {
      this.name = name
      this.age = age
      this.sex = sex
    }

    getName(){
      console.log(`早晨,我系${this.name}`)
    }
    getAge() {
      console.log(`我今年${this.age}`)
    }
    getJob(job?: string) {
      console.log(`我的职业是${job || '自由职业'}`)
    }
  }
  // 创建实例
  const linna = new Person('Linna', 30, 'female')
  // 调用实例的方法
  linna.getName() // 早晨,我系Linna
  console.log(linna)

# 继承

通过继承扩展类,接👆🌰,子/派生类父/基类继承了属性和方法,👇🌰中的子类Coder通过关键字extends继承了父类Person的属性和方法,所以可以调用父类的getName等方法

  class Coder extends Person {
    code() {
      console.log('敲了几十万行代码')
    }
  }

  const coder = new Coder('程序猿')
  coder.getName() // 调用父类的方法
  coder.code() // 敲了几十万行代码

👇来个复杂点的🌰,这里创建了两个子类,里面都有一个构造函数constructor,子类中的constructor必须调用super,即父类的构造函数

在构造函数里访问this的属性之前,我们 一定要调用super。 这个是TypeScript强制执行的一条重要规则。

  class Writer extends Person {
    // 调用父类构造方法
    constructor(name: string = '莫言') {
      // 派生/子类的构造函数必须包含 "super" 调用
      //this.name = name // 报错: 访问派生类的构造函数中的 "this" 前,必须调用 "super"
      super(name)
    }
    // 重写方法
    getJob() {
      console.log(`我的职业是作家`)
    }
    write(num: number = 1000) {
      console.log(`今天写了${num}`)
    }
  }

  class Sportsman extends Person {
    constructor(name: string = '詹姆斯') {
      super(name)
    }
    getJob() {
      console.log(`我的职业是运动员`)
    }
    playBasketball(num: number = 200) {
      console.log(`今天投了${num}个三分`)
    }
  }
  const writer = new Writer('老舍')
  writer.getJob() // 我的职业是作家
  writer.write() // 今天写了1000字

  const sportsman = new Sportsman('奥尼尔')
  sportsman.getJob()
  sportsman.playBasketball()

这里不仅继承了父类的方法,而且还重写了父类的方法,使父类的方法根据不同的类有不同的功能,不仅如此还也可拥有自身的方法。

# 多态

  // 父类型引用指向子类型实例
  const curry: Person = new Sportsman('库里')
  // 虽然声明为父类型,但是仍会调用子类重写的方法
  curry.getJob() // 我的职业是运动员

  // 当子类型有扩展方法时,不能让子类型引用指向父类型的实例
  // 报错:类型 "Person" 中缺少属性 "playBasketball",但类型 "Sportsman" 中需要该属性。
  const yao: Sportsman= new Person('姚明')

  // 不会报错
  const luxun: Writer = new Person('鲁迅')
  luxun.getName()

# 修饰符

# public

在TypeScript里,因为属性都默认为public,表示公共,所以可以在声明它的类的外部自由访问属性。

# private

当属性被设置为private时,表示私有的,类的外部无法访问属性。

# protected

当属性被设置为protected时,表示受保护的,除了子类,类的外部无法访问属性。

# readonly

当属性被设置为readonly时,表示只读的,只读属性必须在声明时或构造函数内初始化。

改造下👆用到的🌰

  // public默认为隐式,一般不需要显示的表示
  class Person {
    public name: string

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

    public eat(food: string = '米饭') {
      console.log(`今日食左${food}`)
    }
  }

  class Child extends Person {
    private cry: boolean = true

    protected sex: string = 'boy'

    readonly mother: string = 'Mom'

    eat(food: string = '奶粉') {
      super.eat(food)
    }
  }

  const child = new Child('ryan')
  // console.log(child.name) // 公共实例可见
  // console.log(child.cry) // 私有的实例🙅‍♂️见 报错: 属性“cry”为私有属性,只能在类“Child”中访问。
  // console.log(child.sex) // 受保护的实例🙅‍♂️见 报错: 属性“sex”受保护,只能在类“Child”及其子类中访问。
  // console.log(child.mother = 'Mum') // 只读属性实例🙅‍♂️重新赋值 报错: 无法分配到 "mother" ,因为它是只读属性。

  class Baby extends Child {
    eat(food: string = '奶') {
      // console.log(this.cry) // 子类🙅访问父类私有属性 报错: 属性“cry”为私有属性,只能在类“child”中访问。
      // console.log(this.sex) // 子类可以访问父类受保护属性
      // console.log(this.mother = 'Mother') // 只读属性子类🙅重新赋值 报错: 无法分配到 "mother" ,因为它是只读属性。

      super.eat(food)
    }
  }

# 总结

修饰符 子类 实例
public 可见 可见
protected 可见 哒咩
private 哒咩 哒咩
readonly 可见🙅改 可见🙅‍♂️改

# getters/setters

Typescript可以使用getters/setters来拦截对象属性的访问。

  class Car {
    brand: string = '品牌'
    series: string = '系列'
    get carInfo () {
      console.log('获取车辆信息')
      return this.brand + '_' + this.series
    }
    set carInfo (val: string) {
      console.log('设置车辆信息', val)
      let vals = val.split('_')
      this.brand = vals[0]
      this.series = vals[1]
    }
  }
  let car = new Car()
  car.brand = '宝马'
  car.series = '3系'
  console.log(car.carInfo) // 获取车辆信息 宝马_3系
  console.log(car.carInfo = '天籁_公爵', car.carInfo)
  // 设置车辆信息 
  // 获取车辆信息
  // 天籁_公爵 天籁_公爵

# static

当属性被设置为static时,表示该属性为类本身的属性,而非实例的属性

  class City {
    name: string = '深圳'
    static name1: string = '台湾'
  }
  let city = new City()
  console.log(city.name)
  // console.log(city.name1) // 实例🙅访问静态属性/方法 属性“name1”在类型“City”上不存在。你的意思是改为访问静态成员“City.name1”吗?
  console.log(City.name1) // 台湾

# abstract

abstract关键字指的是抽象类,用于定义抽象类和抽象类内部的抽象方法。

抽象类作为其他子类的父类,但是它不能实例化,为了让子类实例化及实现内部抽象方法。

抽象类不同于接口的约束作用,它可以包含成员的实现细节。

  // 定义抽象类
  abstract class Animal {
    // 定义抽象类的抽象方法,无具体实现
    abstract sound (): string 
    // 重写run方法
    run() {
      console.log('run! deng~deng~deng')
    }
  }
  // console.log(new Animal()) // 报错: 无法创建抽象类的实例
  class Dog extends Animal {
    sound(): string {
      console.log('叫ing...')
      return 'woof~~~'
    }
    run() {
      return '四条腿跑'
    }
  }
  const puppy = new Dog()
  console.log(puppy.sound()) // 叫ing... woof~~~
  console.log(puppy.run()) // 四条腿跑

# 参数属性

通过设置参数属性,Typescript隐式帮我们创建和初始化类对应的属性,不需要频繁的通过this.属性名这种方式创建和初始化属性,请看👇🌰

  class Person2 {
    constructor (public name: string) {}
    // 等同于
    // name: string
    // constructor (name: string) {
    //   this.name = name
    // }
    
    // 以下修饰符同理  
    // constructor (private name: string) {}
    // constructor (protected name: string) {}
    // constructor (readonly name: string) {}
  }  
  let person2 = new Person2('Luna')
  // 当参数属性为private时,报错:属性“name”为私有属性,只能在类“Person2”中访问。
  // 当参数属性为protected时,报错:属性“name”受保护,只能在类“Person2”及其子类中访问。
  console.log(person2.name)
  // 当参数属性为readonly时,并重新赋值时,报错:无法分配到 "name" ,因为它是只读属性;不重新赋值不报错
  // console.log(person2.name = 'James')