Skip to content

TypeScript 的函数类型

简介

函数的写法有两种。

ts
// 写法一
const hello = function<T extends string | number>(p: T):T {
  return p 
};

// 写法二
type Foo = <T extends string | number>(p: T) => T
const lfoo: Foo = function (p) {
    return p
}

函数类型里面的参数名与实际参数名,可以不一致。

ts
let f: (x: number) => number;

f = function (y: number) {
  return y;
};

数组的forEach()方法的参数是一个函数,该函数默认有三个参数(item, index, array) => void,实际上往往只使用第一个参数(item) => void

因此,TypeScript 允许函数传入的参数不足。

比较小众的是,函数类型还可以采用对象写法

ts
let add: {
  (x: number, y: number): number;
};

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

上面示例中,变量add的类型就写成了一个对象。

函数类型的对象写法如下。

{
  (参数列表): 返回值
}

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

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

ts
function f(x: number) {
  console.log(x);
}
f.version = "1.0";

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

// 也可以提取为interface
interface myfn {
  (a: number, b: number): number;
  version: string
}

Function 类型

TypeScript 提供 Function 类型表示函数,任何函数都属于这个类型

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

上面示例中,参数f的类型就是Function,代表这是一个函数。

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

Function 类型的函数可以接受任意数量的参数,每个参数的类型都是any,返回值的类型也是any,代表没有任何约束,正如之前提到的Object那样,它太宽泛了,失去了 TS 的意义,所以不建议使用。

箭头函数

箭头函数是普通函数的一种简化写法,它的类型写法与普通函数类似。

其中,参数的类型写在参数名后面,返回值类型写在参数列表的圆括号后面。

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

function greet(fn: (a: string) => void): void {
  fn("world");
}

可选参数

如果函数的某个参数可以省略,则在参数名后面加问号表示。

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

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

上面示例中,参数x后面有问号,表示该参数可以省略

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

ts
function f(x?: number) {
  return x;
}

f(undefined); // 正确

但是,反过来就不成立,类型显式设为undefined的参数,就不能省略!!!!!。

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

f(); // 报错

函数的可选参数只能在参数列表的尾部,跟在必选参数的后面。

如果前部参数有可能为空,这时只能显式注明该参数类型可能为undefined

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

参数默认值

TypeScript 函数的参数默认值写法,与 JavaScript 一致。

  • 设置了默认值的参数,就是可选的。
  • 可以省略参数的类型声明,因为可以从默认值推断
  • 可选参数与默认值不能同时使用。
  • 设有默认值的参数,如果传入undefined,也会触发默认值。
TS
function createPoint(x: number = 0, y: number = 0): [number, number] {
  return [x, y];
}

createPoint(); // [0, 0]

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

参数解构

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

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

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

// 结合类型别名
type ABC = { a: number; b: number; c: number };

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

rest 参数

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

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

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

function multiply(n: number, ...m: number[]) {
  return m.map((x) => n * x);
}

readonly 只读参数

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

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

void 类型

void 类型表示函数没有返回值。

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

上面示例中,函数f没有返回值,类型就要写成void

如果返回其他值,就会报错。

void 是否允许返回 undefined 或 null 取决于 strictNullChecks 配置

如果后面使用了这个函数的返回值,就违反了约定,则会报错。

ts
type voidFunc = () => void;

const f: voidFunc = () => {
  return 123;
};

f() * 2; // 报错

never 类型

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

它主要有以下两种情况。

(1)抛出错误的函数。

ts
function fail(msg: string): never {
  throw new Error(msg);
}

(2)无限执行的函数。

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

高阶函数

一个函数的返回值还是一个函数,那么前一个函数就称为高阶函数

下面就是一个例子,箭头函数返回的还是一个箭头函数。

ts
(someValue: number) => {
    return (multiplier: number) => someValue * multiplier;
}

函数重载

根据参数类型不同,执行不同逻辑的行为,称为函数重载

这意味着,该函数内部有处理字符串和数组的两套逻辑,根据参数类型的不同,分别执行对应的逻辑

TypeScript 对于“函数重载”的类型声明方法是,逐一定义每一种情况的类型。

有一些编程语言允许不同的函数参数,对应不同的函数实现。但是,JavaScript 函数只能有一个实现,必须在这个实现当中,处理不同的参数。

ts
function createElement(tag: "a": )HTMLAnchorElement;
function createElement(tag: "canvas"): HTMLCanvasElement;
function createElement(tag: "table"): HTMLTableElement;
function createElement(tag: string): HTMLElement {
  // ...
}

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

ts
class StringBuilder {
  data = "";

  add(num: number): this;
  add(bool: boolean): this;
  add(str: string): this;
  add(value: any): this {
    this.data += String(value);
    return this;
  }

  toString() {
    return this.data;
  }
}

由于重载是一种比较复杂的类型声明方法,为了降低复杂性,一般来说,如果可以的话,应该优先使用联合类型替代函数重载

构造函数

JavaScript 语言使用构造函数,生成对象的实例。

构造函数的最大特点,就是必须使用new命令调用。

ts
const d = new Date();

上面示例中,date()就是一个构造函数,使用new命令调用,返回 Date 对象的实例。

构造函数的类型写法,就是在参数列表前面加上new命令。

ts
class Animal {
  numLegs: number = 4;
}

// 构造函数类型写法
type AnimalConstructor = new () => Animal; 

function create(c: AnimalConstructor): Animal {
  return new c();
}

const a = create(Animal);


// 另一种类型写法,就是采用**对象形式**。
type F = {
  new (s: string): object;
};

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

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

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

手撕

之前考到的两题

一、写一个函数,输入是数字或字符串,返回值的value属性是输入的值

ts
// 这题的关键是考察泛型
type ObjWithValue<T> = {
    value: T
}

function foooo<T extends number | string>(param: T): ObjWithValue<T> {
    return {
        value: param
    }
}

let a = '123'
const ans = foooo(a)
console.log(ans.value)

二、写一个函数,判断一个对象是否是 Promise

ts
// 关键是考察类型守卫

function isPromise<T>(p:any): p is Promise<T>{
	return p instanceof Promise
}
function isPromise2<T>(p: any): p is Promise<T> {
    // return p instanceof Promise
    if (!!p && typeof p === 'object' && typeof p.then === 'function')
        return true
    return false
}


const p:unknown = new Promise(()=>{})
if(isPromise(p)){
	p // 可以调用 .then 且编译器不报错
}

(放在 if 语句里)类型首位的常见形式:

  • typeof
  • instanceof
  • 自定义类型守卫,函数返回一个类型断言,比如上面的函数,返回一个 p is Promise 的断言
  • 字面量类型守卫