• Typescript 函数类型详解


    Typescript 函数

    前言

    虽然 JS/TS 支持面向对象编程,但大部分时候还是在写函数。函数是一等公民。本文介绍下如何在 TypeScript 中使用函数,包括:

    • 函数类型声明
    • 函数参数类型:可选参数、默认参数、剩余参数
    • 函数返回值类型
    • this 类型
    • 函数重载

    函数类型

    面试中经常会被问到,JS 中有哪几种数据类型。其中就会有函数类型。

    JS 中的函数类型很模糊,准确来说,仅有类型的概念,却无类型的实质。好在有了 TS 强类型的加持,现在可以好好定义一个函数的类型了。

    声明一个函数类型,有若干种不同的方式。比如通过 interface 定义函数类型,通过 type 声明函数类型别名,在类声明中声明函数类型等等。

    但核心就一点,只关注函数参数的类型返回值的类型

    下面就从函数的声明入手,来看看函数类型的使用。

    函数的声明方式

    有三种最简单的函数声明方式。

    使用 function 关键字进行声明:

    function add(a: number, b: number): number {
      return a + b
    }
    
    • 1
    • 2
    • 3

    通过函数表达式进行声明:

    let add = function (a: number, b: number): number {
      return a + b
    }
    
    • 1
    • 2
    • 3

    或者使用箭头函数

    let add =  (a: number, b: number): number => {  
        return a + b
    }
    
    • 1
    • 2
    • 3

    上面这三种声明函数的方式和 JS 中声明函数别无二致,就是多了两点:

    • 函数参数需要给明类型
    • 函数返回值也需要给出类型

    那么函数类型到底在哪里呢?把鼠标移动到函数名字上就能看到了:

    image-20221114151636908

    image-20221114151710177

    image-20221114152126540

    和声明普通变量变量一样:

    let name = 'kunwu'
    
    • 1

    如果没有使用类型注解,那么编译器会自动推导出类型来。上面红框标识出来的类型,就是编译器推导出来的。

    函数的类型注解

    函数的类型形如 (参数:类型) => 类型,所以函数的类型注解就是:

    let add: (a: number, b: number) => number = function (a, b) {
      return a + b
    }
    
    • 1
    • 2
    • 3

    通常函数的类型都比较长,此时可以使用 type 类型别名,来声明一个标识符表示该类型:

    type Add = (a: number, b: number) => number
    
    let add: Add => number = function (a, b) {
      return a + b
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样,声明函数类型,使用函数类型,看上去就很简洁直观了。

    对象中的函数类型

    函数的参数

    声明函数时,可以使用类型注解声明参数类型。如果不指定类型,默认类型是 any

    function add (a: number, b: number) :number {
        return a + b
    }
    
    • 1
    • 2
    • 3

    image-20221115004807260

    可选参数

    可选参数使用 ? 标记,需要放到固定参数的后面:

    const fn = (a: number, b?:number) => {
        if(b) {
            return a + b
        } else {
            return a
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可选参数意味着参数可传可不传。通过编译器的类型推导,可以看到好像可选参数等同于一个联合类型:

    image-20221115004923924

    但其实并不等同。

    可选参数表示参数可传可不传,若传则必须是指定的类型。

    而直接将参数类型声明为 string|undefined,意味着这是个必传参数,必须传实参。

    参数默认值

    参数的默认值,在参数的类型后面使用 = 声明:

    const fn = (a: number, b: number = 10): number => {
        return a + b
    }
    
    • 1
    • 2
    • 3

    剩余参数

    剩余参数是 ES6 中的一个特性,使用剩余运算符 ... 来获取函数实参中未被形参声明的变量,其取得的值是一个数组,比如:

    function add(a,b, ...args) {
        console.log(args)
    }
    
    add(1,2,3,4,5)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    则剩余参数 args 就等于 [3, 4, 5]。

    TS 中剩余参数也要有类型声明,需要将其声明为数组类型或者元组类型。

    function add(a: number, b: number, ...args: number[]) {
        console.log(c)
    }
    
    • 1
    • 2
    • 3

    剩余参数和其他的固定参数不同。其他的固定参数声明了类型,则必须传该类型的值。而剩余参数虽然也声明了类型,但可传可不传,可传一个,可传多个,比如:

    function add(a: number, b: number, ...args: number[]) {
        console.log(args)
    }
    
    add(1, 2) // [ ]
    add(1, 2, 3) // [ 3 ]
    add(1, 2, 3, 4) // [ 3, 4 ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    还可以将剩余参数类型声明为元组类型,元组中还可以继续使用可选参数,在参数后使用 ? 表示:

    function log( ...args: [string, number, boolean?]) {
        console.log(args)
    }
    
    log('Shinichi', 17) // [ 'Shinichi', 17 ]
    log('Zoro', 19, true) // [ 'Zoro', 19, true ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    函数的返回值类型

    函数的返回值类型可以通过类型注解指定。如果不指定的话,TS 编译器能够根据函数体的 return 语句自动推断出返回值类型,因此我们也可以省略返回值类型。

    TS 基本类型中有一个 void 类型,表示空类型,它唯一的用处就是用作函数的返回值类型。当一个函数没有 return 语句时,

    image-20221115125205523

    如果函数使用 return 语句返回了 undefined 值,则返回值类型就为 undefined 而不是 void 了:

    image-20221115125323627

    但是此时可以将返回值类型使用类型注解指定为 void:

    image-20221115125425635

    this 类型

    JS 函数中到处可见 this 的身影。关于 this 的指向也是前端八股中的基础之基础。

    默认情况下 TS 编译器会将函数中的 this 设为 any 类型,也就是说编译器不会对 this 做类型检查,就可以任意使用 this,比如:

    function fn() {
        this.name = 'Naruto'
        this.age = 18
    }
    
    • 1
    • 2
    • 3
    • 4

    同时 TS 编译器又提供了一个编译选项 --noImplicitThis,开启后会检查 this 的类型,此时需要明确指定其类型,否则会报错,如下:

    image-20221115150823423

    那么如何为 this 声明类型呢?

    声明 this 类型

    TS 函数中有一个特殊的 this 参数,它用来指定该函数中用到的 this 的类型,需要定义在形参的第一个位置。

    还是上面的例子,要为函数中的 this 指定类型的话,这样写:

    function fn(this: {name: string, age: number}) { 
      this.name = 'Naruto'
      this.xxx = 'xxx'
    }
    
    • 1
    • 2
    • 3
    • 4

    直接在函数参数列表中声明 this 类型不太优雅,可以使用 type 关键字声明别名再使用:

    type Person = {name: string, age: number}
    
    function fn(this: Person) { 
      this.name = 'Naruto'
      this.age = 18
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    当定义了 this 类型后,函数在调用时也会有所不同,需要使用 call、apply :

    type Person = {name: string, age: number}
    
    function fn(this: Person, a: number) { 
      this.name = 'Naruto'
      this.age = 18
    }
    
    
    fn.call({name: 'Naruto', age: 18}, 10)
    
    fn.apply({name: 'Naruto', age: 18}, [10])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    像以前那样直接调用函数是错误的:

    fn(10) // X
    
    • 1

    函数重载

    面向对象编程有三大特征,封装、继承和多态。多态的表现之一就是函数的重载。

    函数重载,就是可以多次声明一个同名函数,但是它们的参数类型不同或者参数个数不同。这样在调用时,可以根据传入参数类型的不同,参数个数的不同,来确定执行的到底是哪一个函数。

    函数重载是后端语言的概念,比如 java 中:

    // 两个整数的和
    public static int add(int a, int b)  {
      return a+b;
    }
    
    // 三个整数的和
    public static int add(int a, int b, int c) {
     return add(a,b)+c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    JS 本身并不支持函数重载。如果多次声明一个函数,则后声明的会覆盖掉先声明的。但是 JS 可以利用 arguments 对象,再加上判断实参的类型,来模拟重载的功能,比如:

    function add() {
       let args = Array.from(arguments)
       if(args.length == 2) {
           return args[0] + args[1]
       } else if(args.length == 3) {
            return args[0] + args[1] + args[3]
       }
    }
    
    add(1,2)
    add(1, 2, 3)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    TS 中多次声明一个同名函数,编译器会报错。但是 TS 提供了实现函数重载的方法:先通过 function 关键字声明重载的类型,最后写函数的实现。

    function add(a: number, b: number) : number
    
    function add(a: string, b: string) : string
    
    function add(a: number|string, b: number | string) {
      if(typeof a === 'number' && typeof b === 'number') {
        return a + b
      }  else if(typeof a === 'string' && typeof b === 'string') {
        return a + b
      } 
    }
    
    add(1, 2)
    add('10', '20')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    由于在声明重载时已经确定了函数的返回值类型,在写函数实现时,就不再需要写返回值类型了。编译器会根据重载类型自动推导。

    TS 的函数重载只是一种伪重载,最终还是要靠去判断类型,来执行不同的逻辑。

    还有一点要注意,声明函数重载和函数实现必须写在一块,中间不能插入其他语句。

    总结

    本文介绍了TS 中有关函数的知识,包括函数的声明方式,如何声明函数类型,函数参数和返回值的类型,函数重载以及 this 的类型。大部分内容和 JS 中差不太多,主要是 this 类型和函数重载这两点,需要额外关注下。

  • 相关阅读:
    病毒消灭战-第13届蓝桥杯Scratch选拔赛真题精选
    【HMS core】【ML Kit】机器学习服务常见问题FAQ(二)
    腹部多器官分割的边界感知网络
    JVM:字节码文件,类的生命周期,类加载器
    java毕业设计大型商场应急预案管理系统mybatis+源码+调试部署+系统+数据库+lw
    计算机毕业设计springboot+vue+elementUI校园疫情防控系统
    Linux命令(126)之help
    团建游戏---小泰山
    传奇外网开服教程-GEE传奇外网全套架设教程
    基于大数据的安防体系建设研究和实践
  • 原文地址:https://blog.csdn.net/Old_Soldier/article/details/127870563