TypeScritp中的函数类型

函数类型

1. 函数类型介绍

函数类型声明,需要在声明函数时,给出参数的类型和返回值的类型。

参数类型直接在参数后接冒号+ 类型;返回值类型在参数圆括号后接冒号+类型。

这里定义的参数类型是string,返回值的类型是void

function hello(txt:string):void {
  console.log('hello ' + txt);
}

如果不指定参数类型,ts会自动推断,如果推断不出来会推断为any类型。

返回值通常可以不写,ts会自己推断,但是有时候出于代码文档目的,返回值类型写上比较好。

1. 函数赋值为一个变量

如果变量被赋值为一个函数,变量的类型有两种写法。

第一种:根据函数类型推断出变量的类型。

第二种:使用箭头函数形式为变量指定类型,参数类型在箭头左侧,返回值类型在箭头右侧。

第二种写法需要注意的是:

  • 函数的参数一定要写在圆括号内,不然会报错。
  • 参数名必须写,不能只写类型,否则会把类型当做参数名,any类型
// 写法一
const hello = function (txt:string) {
  console.log('hello ' + txt);
}

// 写法二
const hello: (txt:string) => void = function (txt) {
  console.log('hello ' + txt);
};

给变量定义类型时,函数类型里面的参数名与实际参数名,可以不一致。

如果一个函数类型定义很长,可以使用type命令定义一个别名。

type MyFunc = (txt:string) => void;

const hello:MyFunc = function (txt) {
  console.log('hello ' + txt);
};

函数的实际参数个数,可以少于类型指定的参数个数,但是不能多,也就是ts允许省略参数。

let myFunc:(a:number, b:number) => number;

myFunc = (a:number) => a; // 正确

myFunc = (
  a:number, b:number, c:number
) => a + b + c; // 报错

如果一个变量要嵌套另一个函数类型,有个技巧就是使用typeof运算符。

这是一个很有用的技巧,任何需要类型的地方,都可以使用typeof运算符从一个值获取类型。

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

const myAdd:typeof add = function (x, y) {
  return x + y;
}

函数类型还可以采用对象的写法。

这种写法的函数参数与返回值之间,间隔符是冒号:,而不是正常写法的箭头=>,因为这里采用的是对象类型的写法,对象的属性名与属性值之间使用的是冒号。

这种写法平时很少用,但是非常合适用在一个场合:函数本身存在属性。

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


function f(x:number) {
  console.log(x);
}
f.version = '1.0';
// 函数f()本身还有一个属性version。这时,f完全就是一个对象,类型就要使用对象的写法。

let foo: {
  (x:number): void;
  version: string
} = f;

函数类型也可以使用 Interface 来声明,这种写法就是对象写法的翻版

interface myfn {
  (a:number, b:number): number;
}

var add:myfn = (a, b) => a + b;

2. Function类型

ts中提供Function类型表示函数,任何函数属于这个类型。

这里参数f的类型就是Function,代表这是一个函数。

function doSomething(f:Function) {
  return f(1, 2, 3);
}

Function 类型的值都可以直接执行。

Function 类型的函数可以接受任意数量的参数,每个参数的类型都是any,返回值的类型也是any,代表没有任何约束,所以不建议使用这个类型,给出函数详细的类型声明会更好。

3. 箭头函数

首先箭头函数是普通函数的一种简化写法,它的类型写法和普通写法类似。参数类型写在参数名的后面,返回值类型写在参数列表的圆括号后面。

const repeat = (
  str:string,
  times:number
):string => str.repeat(times);

当一个函数A的一个参数是函数B,给参数B定义类型,使用的箭头函数形式定义该参数B,此时参数列表外的圆括号外的类型是函数A的返回值类型,B的返回值类型在参数圆括号内存。

function greet(
  fn:(a:string) => void  // void是参数fn的返回值类型
):void { // void是函数greet返回值类型
  fn('world');
}

4. 可选参数

如果函数的某个参数后加上问号,代表该参数可以省略。

function f(x?:number) {
  // ...
}

f(); // OK
f(10); // OK

参数名带有问号,表示该参数的类型实际上是原始类型|undefined,它有可能为undefined。比如,上例的x虽然类型声明为number,但是实际上是number|undefined

但是如果显式定义为undefined则调用时就不能省略了。

function f(x:number|undefined) {
  return x;
}

f() // 报错  这里必须显式写上undefined

函数的可选参数只能在参数列表的尾部,跟在必选参数的后面,否则会报错。

let myFunc:
  (a?:number, b:number) => number; // 报错

如果说参数在前面但是有可能为空,那就得显式定义为undefined,并且为空时,也要传入该参数为undefined才行。

函数体内部用到可选参数时,需要判断该参数是否为undefined

let myFunc:
  (a:number, b?:number) => number; 

myFunc = function (x, y) {
  if (y === undefined) {
    return x;
  }
  return x + y;
}

5. 参数默认值

如果设置默认值,就默认是可选参数,不传该参数,就会等于默认值。只有当参数为undefined时才会触发默认值。

function createPoint(
  x:number = 0,
  y:number = 0
):[number, number] {
  return [x, y];
}

createPoint() // [0, 0]

可选参数与默认值不能同时使用。

// 报错
function f(x?: number = 0) {
  // ...
}

具有默认值的参数如果不位于参数列表的末尾,调用时不能省略,如果要触发默认值,必须显式传入undefined

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

add(1) // 报错
add(undefined, 1) // 正确

6. 参数解构

函数参数如果存在变量解构,类型写法如下。

function f(
  [x, y]: [number, number]
) {
  // ...
}

function sum(
  { a, b, c }: {
     a: number;
     b: number;
     c: number
  }
) {
  console.log(a + b + c);
}

参数解构可以结合类型别名(type 命令)一起使用,代码会看起来简洁一些。

type ABC = { a:number; b:number; c:number };

function sum({ a, b, c }:ABC) {
  console.log(a + b + c);
}

7. rest剩余参数

rest参数表示函数剩余所有的参数,它可以是数组(剩余参数类型相同),也可以是元组(剩余参数类型不同)

// rest 参数为数组
function joinNumbers(...nums:number[]) {
  // ...
}

// rest 参数为元组
function f(...args:[boolean, number]) {
  // ...
}

元组需要声明每一个剩余参数的类型。如果元组里面的参数是可选的,则要使用可选参数。

function f(
  ...args: [boolean, string?]
) {}

rest 参数甚至可以嵌套。

function f(...args:[boolean, ...string[]]) {
  // ...
}

rest 参数可以与变量解构结合使用。

function repeat(
  ...[str, times]: [string, number]
):string {
  return str.repeat(times);
}

// 等同于
function repeat(
  str: string,
  times: number
):string {
  return str.repeat(times);
}

8. readonly只读参数

如果函数内部不能修改某个参数,可以在函数定义时,在参数类型加上readonly关键字,表示这个参数是只读的。

function arraySum(
  arr:readonly number[]
) {
  // ...
  arr[0] = 0; // 报错
}

9. void类型

void类型表示函数没有返回值,如果是函数字面量有返回值就会报错。

function f():void {
  console.log('hello');
}

void 类型允许返回undefinednull

如果打开了strictNullChecks编译选项,那么 void 类型只允许返回undefined。如果返回null,就会报错。这是因为 JavaScript 规定,如果函数没有返回值,就等同于返回undefined

// 打开编译选项 strictNullChecks

function f():void {
  return undefined; // 正确
}

function f():void {
  return null; // 报错
}

问题

如果变量、对象方法、函数参数是一个返回值为void类型的函数,它可以接受返回任意值的函数,只要是你不对结果再有任何处理。

解释:因为有时候传入的函数是有返回值,但是不会产生作用就没有事情,也就不会报错

例子:

push方法有返回值,返回插入新元素后的数组长度,但是他没有任何作用

const src = [1, 2, 3];
const ret = [];

src.forEach(el => ret.push(el));

如果一个函数的运行结果如果是抛出错误,可以将返回值写成void

function throwErr():void {
  throw new Error('something wrong');
}

10. never类型

never类型表示肯定不会出现的值,它用在函数的返回值,表示某个函数肯定不会返回值,即函数不会正常执行结束。函数没有执行结果,不可能有返回值

应用

  • 抛出错误的函数

只有抛出错误才是never类型,如果是return一个Error对象,则返回值是Error类型。

抛出错误的情况属于never、void类型,所以从返回值类型中不知道,抛出的是哪一种错误。

function fail(msg:string):never {
  throw new Error(msg);
}
  • 无限执行的函数

这里while语句,判断条件一直是true,程序会一直循环,不会停止。

const sing = function():never {
  while (true) {
    console.log('sing');
  }
};

如果一个函数抛出了异常或者陷入死循环,那么该函数就无法正常返回一个值,这个函数的返回类型就是never。

而如果程序调用了一个返回值类型为never的函数,就意味着程序会在该函数的调用位置终止,永远不会执行后续的代码。

一个函数如果某些条件下有正常返回值,另一些条件下抛出错误,这时它的返回值类型可以省略never

never类型和void类型区别:never类型表示函数没有执行结束,不可能返回值;void类型表示函数正常执行结束,但是不返回值或者说返回undefined。

11. 局部类型

函数内部声明其他类型,该类型只在函数内部有效,称为局部类型。

function hello(txt:string) {
  type message = string;
  let newTxt:message = 'hello ' + txt;
  return newTxt;
}

const newTxt:message = hello('world'); // 报错

12. 高阶函数

一个函数的返回值还是一个函数,该函数就被称为高阶函数。

(someValue: number) => (multiplier: number) => someValue * multiplier;

13. 函数重载

有的函数可以接受不同类型或者不同个数的参数,可能参数类型的不同,会有不同的逻辑,就称为函数重载。对象的方法也能重载。

ts对于声明函数重载类型,要逐一定义每一种情况的类型,然后再定义完整的类型声明。

函数reverse有两种参数情况,都声明了,并且还有一个完整的类型声明,在完整的类型声明里面根据类型的不同处理不同逻辑的代码。

function reverse(str:string):string;
function reverse(arr:any[]):any[];
function reverse(
  stringOrArray:string|any[]
):string|any[] {
  if (typeof stringOrArray === 'string')
    return stringOrArray.split('').reverse().join('');
  else
    return stringOrArray.slice().reverse();
}

虽然函数的具体实现里面,有一个完整的类型声明,但是在函数实际调用类型时,要以最前面的类型声明为准。

重载声明的排序很重要,因为ts是按照顺序进行检查的,只要发现符合某个类型,就不再往下检查了,所以应该将类型最宽的声明放在最后面,防止覆盖其他类型声明。

在一一定义每一种类型声明时,可以用对象表示,这样更简洁

type CreateElement = {
  (tag:'a'): HTMLAnchorElement;
  (tag:'canvas'): HTMLCanvasElement;
  (tag:'table'): HTMLTableElement;
  (tag:string): HTMLElement;
}

14. 构造函数

第一种:就是在参数列表前加上new命令。

type AnimalConstructor = new () => Animal;

第二种:采用对象形式

type F = {
  new (s:string): object;
};

某些函数既是构造函数,又可以当作普通函数使用,比如Date()。这时,类型声明可以写成下面这样。

type F = {
  new (s:string): object;
  (n?:number): number;
}

上面示例中,F 既可以当作普通函数执行,也可以当作构造函数使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值