• Typescript面向对象---下篇


    ts专栏 ===> 🌈 typescript入门到拔高🌈(持续更新中…)

    接口

    接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。

    在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型
    接口: 是对象的状态(属性)和行为(方法)的抽象(描述)
    接口类型的对象 :
    1. 多了或者少了属性是不允许的
    2. 可选属性: ?
    3. 只读属性: readonly

    现在我们做一个需求帮助大家更好的理解接口:创建人的对象, 需要对人的属性进行一定的约束

    id是number类型, 必须有, 只读的
    name是string类型, 必须有
    age是number类型, 必须有
    sex是string类型, 可以没有

    // 定义人的接口
    interface IPerson {
      id: number
      name: string
      age: number
      sex: string
    }
    
    const person1: IPerson = {
      id: 1,
      name: 'james',
      age: 37,
      sex: '男'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    注意: 类型检查器会查看对象内部的属性是否与 IPerson 接口描述一致, 如果不一致就会提示类型错误

    可选属性

    接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。

    interface IPerson {
      id: number
      name: string
      age: number
      sex?: string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个 ? 符号

    可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误

    const person2: IPerson = {
      id: 1,
      name: 'james',
      age: 37
      // sex: '男' // 可以没有
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    只读属性

    一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly 来指定只读属性:

    interface IPerson {
      readonly id: number
      name: string
      age: number
      sex?: string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    一旦赋值后再也不能被改变了。

    const person2: IPerson = {
      id: 2,
      name: 'harden',
      age: 33
      // sex: '男' // 可以没有
      // score: 12 // error 没有在接口中定义, 不能有
    }
    person2.id = 13 // error
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • readonly vs const
      • 最简单判断该用 readonly 还是 const 的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 const,若做为属性则使用 readonly

    函数类型

    接口能够描述 JavaScript 中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。

    为了使用接口表示函数类型,我们需要给接口定义一个调用签名。它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。

    /*
    接口可以描述函数类型(参数的类型与返回的类型)
    */
    
    interface SearchFunc {
      (source: string, subString: string): boolean
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。

    const mySearch: SearchFunc = function(source: string, sub: string): boolean {
      return source.search(sub) > -1
    }
    
    console.log(mySearch('abcd', 'bc'))
    
    • 1
    • 2
    • 3
    • 4
    • 5

    类类型

    定义类时,可以使类实现一个接口,实现接口就是使类满足接口的要求,实现接口需要使用一个关键字implements

    类实现接口

    interface Alarm {
      alert(): any
    }
    
    interface Light {
      lightOn(): void
      lightOff(): void
    }
    
    class Car implements Alarm {
      alert() {
        console.log('Car alert')
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    一个类可以实现多个接口

    class Car2 implements Alarm, Light {
      alert() {
        console.log('Car alert')
      }
      lightOn() {
        console.log('Car light on')
      }
      lightOff() {
        console.log('Car light off')
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    接口继承接口

    和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。

    interface LightableAlarm extends Alarm, Light {}
    
    • 1

    泛型

    定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。

    • 举例
      function test(arg: any): any{
          return arg;
      }
      
      • 1
      • 2
      • 3
    • 上例中,test函数有一个参数类型不确定,但是能确定的是其返回值的类型和参数的类型是相同的,由于类型不确定所以参数和返回值均使用了any,但是很明显这样做是不合适的,首先使用any会关闭TS的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型,这样的话跟js还有什么区别呢?
    • 使用泛型:
      function test<T>(arg: T): T{
          return arg;
      }
      
      • 1
      • 2
      • 3
    • 这里的 就是泛型,T是我们给这个类型起的名字(不一定非叫T),设置泛型后即可在函数中使用T来表示该类型。所以泛型其实很好理解,就表示某个类型。
    • 那么如何使用上边的函数呢?
      • 方式一(直接使用):
        • test(10)
        • 使用时可以直接传递参数使用,类型会由TS自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式
      • 方式二(指定类型):
        • test(10)
        • 也可以在函数后手动指定泛型

    多个泛型参数的函数

    一个函数可以定义多个泛型参数,泛型间使用逗号隔开:

    function test<T, K>(a: T, b: K): K{
        return b;
    }
    
    test<number, string>(10, "hello");
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用泛型时,完全可以将泛型当成是一个普通的类去使用

    泛型接口

    在定义接口时, 为接口中的属性或方法定义泛型类型在使用接口时, 再指定具体的泛型类型

    interface IbaseCRUD<T> {
      data: T[]
      add: (t: T) => void
      getById: (id: number) => T
    }
    
    class User {
      id?: number //id主键自增
      name: string //姓名
      age: number //年龄
    
      constructor(name, age) {
        this.name = name
        this.age = age
      }
    }
    
    class UserCRUD implements IbaseCRUD<User> {
      data: User[] = []
    
      add(user: User): void {
        user = { ...user, id: Date.now() }
        this.data.push(user)
        console.log('保存user', user.id)
      }
    
      getById(id: number): User {
        return this.data.find(item => item.id === id)
      }
    }
    
    const userCRUD = new UserCRUD()
    userCRUD.add(new User('tom', 12))
    userCRUD.add(new User('tom2', 13))
    console.log(userCRUD.data)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    泛型类

    在定义类时, 为类中的属性或方法定义泛型类型 在创建类的实例时, 再指定特定的泛型类型

    class GenericNumber<T> {
      zeroValue: T
      add: (x: T, y: T) => T
    }
    
    let myGenericNumber = new GenericNumber<number>()
    myGenericNumber.zeroValue = 0
    myGenericNumber.add = function(x, y) {
      return x + y
    }
    
    let myGenericString = new GenericNumber<string>()
    myGenericString.zeroValue = 'abc'
    myGenericString.add = function(x, y) {
      return x + y
    }
    
    console.log(myGenericString.add(myGenericString.zeroValue, 'test'))
    console.log(myGenericNumber.add(myGenericNumber.zeroValue, 12))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    泛型约束

    如果我们直接对一个泛型参数取 length 属性, 会报错, 因为这个泛型根本就不知道它有这个属性

    // 没有泛型约束
    function fn<T>(x: T): void {
      // console.log(x.length)  // error
    }
    
    • 1
    • 2
    • 3
    • 4

    我们可以使用泛型约束来实现

    interface Lengthwise {
      length: number
    }
    
    // 指定泛型约束
    function fn2<T extends Lengthwise>(x: T): void {
      console.log(x.length)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们需要传入符合约束类型的值,必须包含必须 length 属性:

    fn2('abc')
    // fn2(123) // error  number没有length属性
    
    • 1
    • 2

    使用T extends Lengthwise表示泛型T必须是Lengthwise的子类.

    小结

    到这里,面向对象的知识就到尾声了,同时typescript的知识暂停一段落,希望各位能够在这个系列中学到一些东西,主要是需要会应用,一切在实践过后才能够充分掌握。

  • 相关阅读:
    物业管理智慧小区活动报名小程序开发
    “零代码”能源管理平台:智能管理能源数据
    mysql 是不是要创建一个用户 然后给这个用户远程权限 操作msg数据库 而不是给root用户远程权限?
    vue项目npm install报错解决
    【逐步剖C++】-第二章-C++类和对象(下)
    中望CAD 2023 安装教程
    CSS3-圆角边框border-radius 盒子阴影border-shadow
    MySQL8.0 一主二从
    DHTMLX JavaScript Gantt Chart 8.0.5 Crack
    循环结构( for循环、双重for循环)
  • 原文地址:https://blog.csdn.net/m0_52040370/article/details/126249371