blog icon indicating copy to clipboard operation
blog copied to clipboard

typescript手册-函数的类型

Open Pasoul opened this issue 5 years ago • 2 comments

函数的定义方式

函数的定义方式有两种:函数声明函数表达式

// 函数声明
function add(x, y) {
  return x+y;
}
// 函数表达式
let reduce = function(x, y) {
  return x - y;
}

函数的类型

一个函数有输入输出,对函数类型的约束本质上是对输入值和输出值的约束,为上面两个函数添加类型声明

函数声明

函数声明的类型定义较简单:

function add(x:number, y:number):number {
  return x + y;
}

这里我们定义了参数xy的类型为number,返回值类型也为number,除了类型的约束,参数的个数也限制为2个,输入多余或者少于要求的参数,是不被允许的

function add(x:number, y:number):number {
  return x + y;
}
add(1, 2, 3);
// Expected 2 arguments, but got 3
function add(x:number, y:number):number {
  return x + y;
}
add(1);
// Expected 2 arguments, but got 1

函数表达式

我们对上面的函数表达式定义类型:

let reduce = function(x:number, y:number):number {
  return x - y;
}

上面我们仅仅对等号右侧的匿名函数进行类型定义,等号左边的reduce是通过赋值之后类型推导推断出来的,准确的来说叫上下文归类,是类型推导的一种,事实上我们也可以给reduce定义类型:

下面是完整的类型声明:

let reduce: (x:number, y:number) => number = function(x:number, y:number):number {
  return x - y;
}

整个(x:number, y:number) => number是对reduce的类型定义,注意 =>并非是ES6的箭头函数,它表示函数输入输出之间的管道,左侧是函数输入值,右侧是函数输出值

好的习惯

我们多次强调函数是由输入值和输出值组成的,即使函数没有任何返回值,我们也要必须指定返回值类型为void而不能留空。

Pasoul avatar Mar 14 '19 08:03 Pasoul

用接口定义函数的形状

我们可以用接口描述函数的形状

interface ReduceFunc {
  (x:number, y:number):number
}


let reduce:ReduceFunc;
reduce = function(x:number, y:number):number {
  return x - y;
}

可选参数

前面我们提到,如果对函数的参数进行约束,输入多余或者少于要求的参数,是不被允许的,那么我们如何定义可选的参数呢?

function getName(firstName: string, lastName?:string) {
  if (lastName) {
    return firstName + ',' + lastName
  } else {
    return firstName
  }
}
getName("zhangsan");
getName("zhangsan", "lisi");

上面的例子我们通过在参数后加?表示这是一个可选参数,需要注意的是,可选参数后面不能出现必选参数:

function getName(firstName: string, lastName?:string, thirdName) {
  if (lastName) {
    return firstName + ',' + lastName
  } else {
    return firstName
  }
}

这样编译会直接报错: A required parameter cannot follow an optional parameter.

参数默认值

同ES6一样,typescript支持给函数参数设置默认值

function getName(firstName: string, lastName:string = 'jack') {
  if (lastName) {
    return firstName + ',' + lastName
  } else {
    return firstName
  }
}
getName("tom");  // 输出tom,jack

参数默认值是不受可选参数必须在必选参数之后的限制了

function getName(lastName:string = 'jack', firstName: string) {
  return firstName + ',' + lastName
}
getName(undefined, "tom");

但是即使lastName不赋值,也要用undefined占位,否则typescript不知道tom是赋值给lastName还是firstName,编译将会报错

function getName(lastName:string = 'jack', firstName: string) {
  return firstName + ',' + lastName
}
getName("tom");
function getName(lastName:string = 'jack', firstName: string) {
                                                  ~~~~~~~~~~~~~~~~~
    An argument for 'firstName' was not provided.

Pasoul avatar Mar 15 '19 07:03 Pasoul

剩余参数

在ES6中,使用...rest收集函数中的剩余参数

function push(array, ...items) {
  items.forEach(function(item) {
      array.push(item);
  });
}

let a = [];
push(a, 1, 2, 3);

注意rest参数只能是最后一个参数

因为items是一个数组,所有我们可以用数组的类型来定义它

function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}

let a = [];
push(a, 1, 2, 3);

函数重载

javascript里函数根据传入不同的参数而返回不同类型的数据是很常见的。比如我们实现函数reverse,当输入数字123,返回值是反转的321,输入字符串hello,返回值是反转的olleh

function reverse(x:number|string):number|string {
  if (typeof x === 'number') {
    return Number(x.toString().split('').reverse().join(''))
  } else if (typeof x === 'string') {
    return x.split('').reverse().join('')
  }
}

上面的函数能满足我们的需求,但是有一个问题是不能精确的表达出:输入的是数字,输出的也是数字,输入的是字符串,输出的也是字符串

这个时候我们使用重载定义多个reverse函数:

function reverse(x:number):number
function reverse(x:string):string
function reverse(x:number|string):number|string {
  if (typeof x === 'number') {
    return Number(x.toString().split('').reverse().join(''))
  } else if (typeof x === 'string') {
    return x.split('').reverse().join('')
  }
}

上面的例子我们需要注意:

  • 我们重复定义了reverse函数,但前几次是函数定义,只有最后一次是函数实现
  • 编译器会从第一个定义开始查找符合的类型,因此越精确的定义应该放在越前面
  • 最后一次定义function reverse(x:number|string):number|string不会被作为函数重载,因此这里的重载只有两个:一个接受number一个接受string

比如我们再最后一次定义的时候添加了boolean类型的参数和返回值:

function reverse(x:number):number
function reverse(x:string):string
function reverse(x:number|string|boolean):number|string|boolean {
  if (typeof x === 'number') {
    return Number(x.toString().split('').reverse().join(''))
  } else if (typeof x === 'string') {
    return x.split('').reverse().join('')
  }
}
reverse(true)

编译将会报错:

Argument of type 'true' is not assignable to parameter of type 'string'

由此可见我们最后一次定义函数是不会计入重载列表的,编译器在找到sring之后就停止类型检查,抛出异常。

参考:

Pasoul avatar Mar 15 '19 08:03 Pasoul