zoukankan      html  css  js  c++  java
  • 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}
  • 相关阅读:
    JAVA 线程安全与同步机制
    JAVA 多线程
    el-table 宽度自适应bug
    详解迭代器Iterator
    理解基本类型的溢出
    理解classpath
    I/O(一):基础知识
    C++: 智能指针
    C++: 值类别与移动语义基础
    CUDA 架构与编程概述
  • 原文地址:https://www.cnblogs.com/wuqilang/p/15471569.html
Copyright © 2011-2022 走看看