TypeScript函数

303 阅读6分钟

在 TypeScript 中,函数类型是一种重要的类型,它允许我们明确地定义函数的参数和返回值类型。先通过几个例子来看看函数类型的定义:

  1. 函数声明: TypeScript 允许使用类似 JavaScript 的语法来声明函数。
function add(x: number, y: number): number {
    return x + y;
}
  1. 函数类型: 可以使用类型标注为函数定义类型(类似于JavaScript中的函数表达式)。
let myAdd: (x: number, y: number) => number = function (x, y) {
    return x + y;
};

在这种情况下,我们一般会用类型别名将函数类型抽离出来:

type AddFunc = (x: number, y: number) => number
let myAdd: AddFunc = function (x, y) {
    return x + y;
};
  1. 使用接口定义函数类型:
type BinaryFunction = {
    (x: number, y: number): number;
}

let myAdd: BinaryFunction = function (x, y) {
    return x + y;
};

通过这种方式定义,还可为函数添加其他属性:

type DescribableFunction = {
  description: string;
  (someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
  console.log(fn.description + " returned " + fn(6));
}

function myFunc(someArg: number) {
  return someArg > 3;
}
myFunc.description = "default description";

doSomething(myFunc);

new

JavaScript 中的函数也可以使用 new 运算符来调用。TypeScript 将这类函数称为构造函数,因为它们通常用于创建新的对象。可以通过在调用签名前加上 new 关键字来编写构造函数的签名:

type SomeConstructor = {
    new (s: string): SomeObject;
};

function fn(ctor: SomeConstructor) {
    return new ctor("hello");
}

一些对象,例如 JavaScript 中的 Date 对象,可以在调用时使用 new 也可以不使用 new。可以在同一类型中随意组合调用和构造签名:

type CallOrConstruct = {
  (n?: number): string;
  new (s: string): Date;
};

可选参数

在实际使用函数的过程中,我们对参数的定义可能会更加灵活。比如有些参数是可选的,如我们在对象中使用?描述对象可选属性一样,在函数类型中,我们也使用?来描述一个可选参数:

// 使用 ? 表示参数为可选
function greet(name: string, greeting?: string): string {
    if (greeting) {
        return greeting + ' ' + name;
    } else {
        return 'Hello ' + name;
    }
}

// 调用函数时可以省略可选参数
console.log(greet('Alice'));          // 输出: Hello Alice
console.log(greet('Bob', 'Hi'));      // 输出: Hi Bob

在这个例子中,greeting 参数被标记为可选,因此在调用 greet 函数时可以选择是否提供该参数。如果提供了 greeting 参数,它将被使用,否则函数会使用默认的问候语。

需要注意的是:

  • 可选参数必须放在参数列表的最后。
  • 在函数体内,你需要检查可选参数是否为 undefined,因为调用时未提供可选参数时它的值为 undefined

还可以使用默认参数来模拟可选参数的行为:

function greet(name: string, greeting = ‘Hello’): string {
    return greeting + ' ' + name;
}

console.log(greet('Alice'));          // 输出: Hello Alice
console.log(greet('Bob', 'Hi'));      // 输出: Hi Bob

但可选参数提供了更明确的语法,使得函数接口更易于理解和使用。

函数重载

当面临一些稍微复杂的应用场景时,以上基础类型定义就无法满足要求。例如,编写一个生成日期的函数,该函数接受时间戳(一个参数)或月/日/年(三个参数)的规范。如果采用我们已知的方式——使用可选参数,则会出现当函数接收两个参数时,函数产出了我们不想要的结果:

function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  if (d !== undefined && y !== undefined) {
    return new Date(y, mOrTimestamp, d);
  } else {
    return new Date(mOrTimestamp);
  }
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
//违背了设计初衷,且产出了意外的数据,有可能导致后续的bug
const d3 = makeDate(1, 3);

这个时候我们就需要使用函数重载,明确定义函数可接收的参数组合,在类型检查阶段暴露出不合法的函数调用:

function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  if (d !== undefined && y !== undefined) {
    return new Date(y, mOrTimestamp, d);
  } else {
    return new Date(mOrTimestamp);
  }
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
//Error-没有接收2个参数的重载类型,但存在接收1个或3个参数的重载类型。
const d3 = makeDate(1, 3);

在这个例子中,makeDate 函数有三部分:

  1. 函数签名1: 接受一个时间戳参数,返回一个日期。
  2. 函数签名2: 接受月/日/年三个参数,返回一个日期。
  3. 实现部分: 实际的函数体,函数的实现签名,会包含重载签名的所有可能情况,根据参数的类型执行不同的逻辑。

需要注意的是,实际的函数体部分只会被编译为一份 JavaScript 代码,因此在运行时并不会出现多个函数定义。函数重载主要是在编译阶段用于类型检查和提示的机制。

rest参数

除了使用可选参数或重载来创建能够接受各种固定参数数量的函数之外,我们还可以使用 rest 参数来定义可以接受不定数量参数的函数:

// 使用 rest 参数来接受不定数量的参数
function sum(...numbers: number[]): number {
    return numbers.reduce((total, num) => total + num, 0);
}

// 调用函数,可以传递任意数量的参数
console.log(sum(1, 2, 3));        // 输出: 6
console.log(sum(1, 2, 3, 4, 5));  // 输出: 15
console.log(sum(10));             // 输出: 10
console.log(sum());               // 输出: 0

在这个例子中,sum 函数使用了 rest 参数 ...numbers,它将所有传递给函数的参数收集到一个名为 numbers 的数组中。函数内部使用 reduce 方法对数组中的所有数字进行求和,最终返回总和。

Rest 参数的类型是一个数组,因此在函数体内可以按照数组的方式处理它。这种方式非常灵活,允许我们编写接受不同数量参数的函数,而无需在函数声明中明确指定每个参数。

void 类型

在 TypeScript 中,一个没有返回值(即没有调用 return 语句)的函数,其返回类型应当被标记为 void 而不是 undefined,即使它实际的值是 undefined。

// 没有调用 return 语句
function func1(): void { }

// 调用了 return 语句,但没有返回值
function func2(): void {
  return;
}

在 TypeScript 中,undefined 类型是一个实际的、有意义的类型值,而 void 才代表着空的、没有意义的类型值。  相比之下,void 类型就像是 JavaScript 中的 null 一样。因此在我们没有实际返回值时,使用 void 类型能更好地说明这个函数没有进行返回操作。但在上面的第二个例子中,其实更好的方式是使用 undefined :

function func2(): undefined {
  return;
}

此时我们想表达的则是,这个函数进行了返回操作,但没有返回实际的值

总结

以上我们介绍了在TypeScript中函数的三种定义方式:函数声明、函数类型、接口定义,也提到了new函数的特殊定义方式。

然后根据不同的应用场景,分别介绍了可选参数、函数重载、rest参数,顺便介绍了一种特殊的返回类型void,并说明了它跟undefined的区别。

另外,关于泛型函数我们暂时没做介绍,等到介绍完有关泛型类型之后再做进一步介绍。