ts面向对象OOP

面向对象

面向对象是程序中一个非常重要的思想,简而言之就是程序中所有的操作都需要通过对象来完成,对象中有属性和方法

举例:

  操作浏览器要使用window对象

  操作网页要使用document对象

  操作控制台要使用console对象

要想面向对象,操作对象,首先要拥有对象

要创建对象,必须要先定义类,所谓的类可以理解为对象的模型

程序中可以根据类创建指定类型的对象

举例来说:

可以通过Person类来创建人的对象,通过Dog类创建狗的对象,不同的类可以用来创建不同的对象

 

定义类

/*
  使用class关键字定义类

  属性有2种:
    1、实例属性
      定义:直接定义
      使用:通过new关键字创建一个实例对象,对象去调用该属性
    2、静态属性,也叫类属性
      定义:在定义该属性前加上static关键字
      使用:直接通过类名访问该属性

  方法和属性一样,加上static就是静态方法
*/
class Person {
  readonly name: string = '小明' // 只读属性
  age: number = 19
  static age: number = 18
  sayHello() {
    console.log('hello')
  }
}

const p = new Person()
console.log(p) // Person {name: '小明', age: 19}
// p.name = 'xx' // 只读属性不可赋值
p.age = 88
console.log(p.name) // 小明
console.log(p.age) // 88
p.sayHello() // hello

console.log(Person.age) // 18

构造函数和this

  在new一个对象时会调用constructor函数

class Dog {
  // 1、在类中定义属性
  name: string
  age: number
  bark() {
    // 方法中的this指向调用该方法的对象
    console.log('汪汪汪', this)
  }
  // 2、在构造函数中进行赋值
  constructor(name: string, age: number) {
    // new Dog()时执行构造函数,这里的this指向实例对象
    this.name = name
    this.age = age
  }
}
const dog = new Dog('旺财', 3)
const dog1 = new Dog('阿黄', 4)
console.log(dog) // {name: '旺财', age: 3}
console.log(dog1) // {name: '阿黄', age: 4}
dog.bark() // 汪汪汪 {name: '旺财', age: 3}
dog1.bark() // 汪汪汪 {name: '阿黄', age: 4}

继承

  使用extends继承后,子类拥有父类所有的属性和方法

  通过继承可以将多个类中公有的代码写在父类中,重复的代码只需写一次

  如果子类中添加了和父类重名的属性和方法,则会覆盖父类中的属性和方法,这种形式叫重写

;(function () {
  class Animal {
    name: string
    age: number
    constructor(name: string, age: number) {
      this.name = name
      this.age = age
    }
    sayHello() {
      console.log('hello~')
    }
  }
  class Dog extends Animal {
    age: number = 100 // 和父类属性名重名,这里的优先级高
    // 和父类方法名重名,这里的优先级高
    sayHello() {
      console.log('汪汪汪')
    }
    run() {
      console.log(`${this.name}在跑`)
    }
  }
  class Cat {
    name: string
    age: number
    constructor(name: string, age: number) {
      this.name = name
      this.age = age
    }
    sayHello(str: string) {
      console.log('喵喵喵', str)
    }
  }
  const dog = new Dog('旺财', 3)
  console.log(dog)
  dog.sayHello()
  dog.run()
  const cat = new Cat('小花', 2)
  console.log(cat)
  cat.sayHello('123')
})()

super

  在类的方法中,super表示当前类的父类(也叫超类)

  如果在子类中写了构造函数,子类的构造函数中必须对父类的构造函数进行调用,也就是执行super()

;(() => {
  class Animal {
    name: string
    constructor(name: string) {
      this.name = name
    }
    sayHello() {
      console.log('hello')
    }
  }
  class Dog extends Animal {
    age: number
    // 如果在子类中写了构造函数,在子类的构造函数中必须对父类的构造函数进行调用
    constructor(name: string, age: number) {
      super(name) // 调用父类的构造函数
      this.age = age
    }
    sayHello() {
      // super.sayHello() // 在类的方法中,super表示当前类的父类
      console.log('汪汪汪')
    }
  }
  const dog = new Dog('旺财', 3)
  console.log(dog)
  dog.sayHello()
})()

多态

  父类定义了一个方法不去实现,让继承它的子类去实现(方法重写),每一个子类都有不同的表现

;(() => {
  // 多态:父类型的引用指向了类型的对象,不同类型的对象针对相同的方法,产生了不同的行为
  class Animal {
    constructor(public name: string) {
      this.name = name
    }
    run(distance: number = 0) {
      console.log(`${this.name}跑了 ${distance} 米`)
    }
  }

  class Dog extends Animal {
    constructor(name: string) {
      super(name) // 调用父类的构造函数,实现子类中属性的初始化
    }
    // 重写父类中的实例方法
    run(distance: number = 5) {
      console.log(`${this.name}跑了 ${distance} 米`)
    }
  }

  class Pig extends Animal {
    constructor(name: string) {
      super(name)
    }
    run(distance: number = 10) {
      console.log(`${this.name}跑了 ${distance} 米`)
    }
  }

  const a: Animal = new Animal('动物')
  a.run()
  const dog: Dog = new Dog('阿黄')
  dog.run()
  const pig: Pig = new Pig('八戒')
  pig.run()
  console.log('--------------')
  // 父类类型创建子类的对象
  const dog1: Animal = new Dog('哮天犬')
  dog1.run()
  const pig1: Animal = new Pig('猪猪侠')
  pig1.run()
  console.log('>>>>>>>>>>>>>>>')
  function showRun(obj: Animal) {
    obj.run()
  }
  showRun(dog1)
  showRun(pig1)
})()

抽象类abstract(ts新增)

  一般用来给子类继承的父类,也可以被当做构造函数去创建对象使用,在该父类前加上abstract就可以将该类变为抽象类,即不能用来创建对象,抽象类是专门用来被继承的类

  抽象方法:抽象类中可以添加抽象方法,就是在方法名前加上abstract

      抽象方法没有方法体,派生类必须对抽象方法进行重写

      抽象方法只能写在抽象类中

 

;(function () {
  abstract class Animal {
    name: string
    constructor(name: string) {
      this.name = name
    }
    abstract sayHello(): void // 抽象方法没有方法体,只能定义在抽象类中
  }
  class Dog extends Animal {
    sayHello() {
      // super.sayHello() // 在类的方法中,super表示当前类的父类
      console.log('汪汪汪')
    }
  }
  class Cat extends Animal {
    sayHello() {} // 子类必须对抽象方法进行重写
  }
  const dog = new Dog('旺财')
  console.log(dog)
  dog.sayHello()
})()

接口(ts新增)

  接口可以当成类型声明去使用

  接口用来定义一个类的结构,指定一个类中应该包含哪些属性的方法

  接口中所有的属性都没有实际的值;所有的方法都是抽象方法,没有方法体

 

  接口和抽象类的区别:

    抽象类既可以有抽象方法也可以有普通方法,接口只能有抽象方法

    抽象类使用extends继承,接口使用implement实现

;(function () {
  // 类型声明
  type myType = {
    name: string
    age: number
  }
  // 接口当成类型声明去使用:接口定义一个对象的结构
  interface myInterface {
    name: string
    age: number
  }
  // 接口可以重复定义
  interface myInterface {
    gender: string
  }
  const obj: myType = {
    name: '小明',
    age: 18
  }
  const obj1: myInterface = {
    name: '小红',
    age: 17,
    gender: '男'
  }

  // 接口在定义类的时候限制类的结构 接口中所有的属性都没有实际的值;所有的方法都是抽象方法,都没有方法体
  interface myInter {
    name: string
    sayHello(): void
  }
  // 定义类时,使类实现一个接口,满足接口的要求
  class Person implements myInter {
    name: string
    constructor(name: string) {
      this.name = name
    }
    sayHello() {
      console.log('hello')
    }
  }
})()

属性的封装

  ts可以在类中的属性前添加属性的修饰符(之前用过static和readonly)

    1、public  默认值,修饰的属性可以在任意位置(当前类、子类、类的外部)访问和修改

    2、private  私有属性,只能在当前类中访问和修改(子类、类外部无法访问和修改),但可以通过在类中添加方法使得私有属性可以被外部访问和修改

    3、protected  受保护的属性,只能在当前类和当前类的子类中访问和修改(类外部无法访问和修改)

 

  封装属性

  封装属性是什么:

    js中,属性封装就是给属性加上getter和setter方法,在访问和修改时使用对应的getter和setter方法

  封装属性的原因:

    由于属性是在new对象时设置的,属性可以被任意的修改,存在安全隐患,因此需要对属性进行封装

  js和ts封装属性的区别:

    1、js封装的属性存取器使用时需要调用对应的getter和setter方法,较为麻烦

    2、ts封装的属性存取器使用时直接当做变量来访问和修改,比较简洁

  封装属性的使用场景:

    1、如果需要对属性的值做安全判断,就需要使用属性封装

    2、在类中使用private修饰的属性,进行访问和修改的时候需要用到封装属性

;(() => {
  class Person {
    private _name: string
    private _age: number
    constructor(name: string, age: number) {
      this._name = name
      this._age = age
    }
    // 被private修饰的属性,在外部不可以直接访问,可以在类里面定义一个方法使得私有属性可以被外部访问
    getName() {
      return this._name
    }
    // 定义一个方法使得私有属性可以被外部修改
    setName(name: string) {
      this._name = name
    }
    // getName和setName是js中的属性存取器,getter方法访问,setter设置   ts中提供了更简便属性存取器的方式,使用时直接p.name进行访问和设置
    get age() {
      return this._age
    }
    set age(age: number) {
      if (age >= 0) this._age = age // 设置时对属性进行一些判断,提高安全性
    }
  }
  const p = new Person('小明', 12)
  // p._name = 'xx' // public修饰的属性可以直接这么修改
  console.log(p)
  // console.log(p._name) // public修饰的属性可以直接这么访问
  // 被private修饰的属性,访问和修改需要通过类中定义的方法来进行,这样做的好处是,可以在设置时对属性进行一些判断,提高安全性
  console.log(p.getName())
  p.setName('小红')
  console.log(p.getName())
  console.log(p.age)
  p.age = 100
  console.log(p.age)
})()

  protected

;(() => {
  class A {
    protected age: number
    constructor(age: number) {
      this.age = age
    }
  }
  class B extends A {
    getName() {
      return this.age // 被protected修饰的属性,可以在当前类和它的子类中进行访问和修改
    }
  }
  const b = new B(20)
  console.log(b)
  console.log(b.age) // 被protected修饰的属性,在类的外部无法访问和修改
})()

  在定义类时可以直接将属性定义在构造函数中,实际上是语法糖

;(() => {
  class Dog0 {
    name: string
    age: number
    constructor(name: string, age: number) {
      this.name = name
      this.age = age >= 0 ? age : 0
    }
  }
  // 在定义类时可以直接将属性定义在构造函数中,实际上是语法糖
  class Dog {
    constructor(public name: string, public age: number) {
      this.age = age >= 0 ? age : 0 // 如果需要对属性进行初始值判断可以加上这句
    }
  }
  const dog = new Dog('大黄', -2)
  console.log(dog)
})()

泛型(ts新增)

// 泛型:不确定的类型,在定义的时候不确定,在执行的时候明确类型。
// 使用场景:在定义函数或类时,遇到类型不明确时使用泛型
function fn<T>(a: T): T {
  return a
}
const res = fn(10) // 可以直接调用具有泛型的函数,不指定泛型,ts可以自动推断类型
console.log(res)
const res1 = fn<string>('hello') // 手动指定类型(严谨,推荐手动指定)
console.log(res1)

// 泛型可以同时指定多个
function fn1<A, B>(a: A, b: B): A {
  console.log(b)
  return a
}
fn1<number, string>(100, 'hello')

// 可以对泛型的类型进行限制
interface Inter {
  length: number
}
// A extends Inter 表示泛型A必须是Inter实现类(Inter的子类)  fn2的参数必须要有length属性,可以是数组,对象,字符串... 参数没有length属性会报错,比如数字
function fn2<A extends Inter>(a: A): number {
  return a.length
}
fn2([])
fn2({ length: 10 })
fn2('xxx')
// fn2(100) // 报错

// 在类中使用泛型
class A<A> {
  name: A
  constructor(name: A) {
    this.name = name
  }
}
const a = new A<string>('小明')
console.log(a) // {name: '小明'}
const a1 = new A<number>(10)
console.log(a1) // {name: 10}

 

posted @ 2021-10-27 16:54  吴小明-  阅读(313)  评论(0编辑  收藏  举报