首页 / TypeScript 入门教程 / 函数类型表达式

TypeScript 入门教程

函数类型表达式

本教程共 80 篇 · 第 21 篇 · 更新于 2026-08-10 · 约 9 分钟阅读

TypeScriptTypeScript 入门教程函数类型类型别名

本节目标:学会三种描述函数类型的方式——箭头式类型别名、调用签名和构造签名,以及理解函数类型之间的兼容性规则。

前面几节我们一直把类型注解写在函数定义里。但有时你需要把”函数类型”本身当作一个独立的类型来使用——比如作为参数类型、变量类型或者返回值类型。TypeScript 提供了几种写法来做这件事。

用类型别名表示函数

最简单的方式是用箭头函数语法定义函数类型:

type GreetFunction = (name: string) => void;

function welcome(fn: GreetFunction) {
  fn("World");
}

// 任何匹配这个签名的函数都可以传入
const sayHello: GreetFunction = (name) => {
  console.log("Hello, " + name);
};

welcome(sayHello); // "Hello, World"

语法 (name: string) => void 表示”一个接受 string 参数、不返回值的函数”。这和箭头函数的写法几乎一样,只不过它表示的是一个类型,不是一个值。

Important

函数类型表达式中的参数名是必须写的。(string) => void 的含义是”一个名叫 string 的参数,类型是 any”,而不是”一个 string 类型的参数”。

type Wrong = (string) => void;
// string 被当作参数名称,类型是 any
// 等价于 (string: any) => void

type Correct = (name: string) => void;
// name 是参数名,string 是参数类型 ✅

类型别名中的参数名和实际函数的参数名不需要一致——TypeScript 只检查类型,不检查名字。

type BinaryOp = (a: number, b: number) => number;

// 参数名不同,类型匹配即可
const add: BinaryOp = (x, y) => x + y;    // ✅
const multiply: BinaryOp = (m, n) => m * n; // ✅

调用签名

函数类型表达式有个局限——它描述不了”一个能调用的对象,同时还有属性”。JavaScript 中函数也是对象,可以挂属性:

function greet(name: string): string {
  return "Hello, " + name;
}
greet.description = "一个问候函数";
greet.version = "1.0.0";

这种”可调用且带属性”的东西,需要用调用签名来描述:

type DescribableFunction = {
  description: string;
  version: string;
  (name: string): string;
};

const greet: DescribableFunction = ((name: string): string => {
  return "Hello, " + name;
}) as DescribableFunction;
greet.description = "一个问候函数";
greet.version = "1.0.0";

console.log(greet("World"));        // "Hello, World"
console.log(greet.description);     // "一个问候函数"

注意调用签名的语法和箭头函数类型的区别:调用签名用冒号 : 分隔参数和返回值,而不是 =>。这是因为调用签名本质上是在对象类型中定义一个属性,冒号是对象属性语法。

// 箭头函数类型表达式
type Fn1 = (x: number) => string;
//         参数      =>  返回值

// 调用签名
type Fn2 = { (x: number): string };
//         参数      :   返回值
Note

调用签名本质上就是把函数当作一个对象来描述。日常开发中,如果你只是简单传一个回调,用箭头函数类型表达式就够了。需要描述”带属性的函数”时,才用调用签名。

构造签名

JavaScript 中有些函数需要用 new 调用来创建实例。TypeScript 用构造签名来描述这种情况:

type SomeConstructor = {
  new (name: string): { name: string };
};

function createInstance(ctor: SomeConstructor, name: string) {
  return new ctor(name);
}

class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

const alice = createInstance(Person, "Alice");
console.log(alice.name); // "Alice"

构造签名就是在调用签名前面加个 new 关键字。像 Date 这种既可以用 new 调用又可以直接调用的对象,可以同时有调用签名和构造签名:

interface CallOrConstruct {
  new (s: string): Date;    // 构造签名:new Date("2025-01-01")
  (): string;               // 调用签名:Date()
}

function handleDate(fn: CallOrConstruct) {
  console.log(fn());           // 普通调用 → string
  console.log(new fn("2025")); // 构造调用 → Date
}

handleDate(Date);

函数类型兼容性

TypeScript 在检查函数类型是否兼容时,有一个重要规则:参数少的函数可以赋值给参数多的函数类型。

type Handler = (a: number, b: number) => number;

const handler1: Handler = (a: number) => a;         // ✅ 一个参数 OK
const handler2: Handler = (a: number, b: number) => a + b; // ✅ 两个参数 OK
const handler3: Handler = (a: number, b: number, c: number) => a + b + c; // ❌ 三个参数不行

这个设计是有意为之的——因为 JavaScript 中可以”少传参数”,这是非常普遍的模式:

const items = [1, 2, 3];

// forEach 的回调类型是 (value, index, array) => void
// 但你可以只写 value,忽略 index 和 array
items.forEach((value) => {
  console.log(value);
});

如果参数个数必须完全匹配,那每写一个 forEach 回调都得把三个参数全写上,太痛苦了。TypeScript 选择兼容 JavaScript 的实际情况:参数可以少,但不能多。

不过返回值类型是严格检查的:

type StringFn = () => string;

const fn1: StringFn = () => "hello";   // ✅
const fn2: StringFn = () => 42;        // ❌ number 不能赋给 string

有一个关于 void 返回值的特例值得记住:当一个函数类型要求返回 void 时,你传入的函数即使有返回值也不会报错:

type VoidCallback = () => void;

// 下面这些都是合法的
const cb1: VoidCallback = () => { return true; };  // ✅
const cb2: VoidCallback = () => 42;                // ✅
const cb3: VoidCallback = function () { return "hi"; }; // ✅

这是因为 Array.prototype.forEach 的回调就要求 void 返回值,但实际中经常传 push 这类有返回值的方法。TypeScript 选择实用优先——不拦着你。

Note

只有在函数类型位置(回调参数的类型声明)中,void 才这么宽松。如果你是直接在函数定义上写 : void 返回值,那还是不能 return 有意义的值的。

小结

三种描述函数类型的方式:箭头式类型别名(最常用)、调用签名(函数也带属性时用)、构造签名(描述构造函数)。函数兼容性的核心是:参数可以少、返回值必须匹配、void 回调位置有宽松规则。下一节进入泛型函数——让一个函数处理多种类型而不丢失类型信息。