📚 TypeScript 教程系列

  1. 入门与配置
  2. 基础类型与变量声明
  3. 函数(本文)
  4. 流程控制与运算符
  5. 集合类型
  6. 异步编程与错误处理
  7. 接口与类
  8. 泛型与类型组合
  9. 高级类型
  10. 模块、装饰器与工程化

⚠️ 来源声明:本文内容参考自 菜鸟教程 TypeScript 教程,仅供学习交流,版权归原作者所有。
TypeScript 在 JavaScript 函数的基础上引入了完整的类型系统,让我们可以为参数、返回值、this 等显式标注类型,并通过函数重载为同一个函数定义多套调用签名。本文从函数的定义与参数类型出发,逐步覆盖可选参数、默认参数、剩余参数、this 绑定,再到函数重载与箭头函数,帮助你写出类型安全且易于维护的函数代码。

函数定义

函数是包裹在花括号中的代码块,前面使用关键词 function 声明。基本语法如下:

1
2
3
function function_name() {
// 执行代码
}

示例:

1
2
3
function test() {
console.log("调用函数");
}

调用函数

函数只有通过调用才可以执行函数内的代码,语法为 function_name()

1
2
3
4
5
function test() {
console.log("调用函数");
}

test();

输出:

1
调用函数

函数返回值

通过 return 语句可以将结果返回到调用处。在使用 return 语句时,函数会停止执行,并返回指定的值。语法格式:

1
2
3
4
function function_name(): return_type {
// 语句
return value;
}

说明:

  • return_type 是返回值的类型
  • return 关键词后跟着要返回的结果
  • 一般情况下,一个函数只有一个 return 语句
  • 返回值的类型需与函数定义的返回类型一致

示例:

1
2
3
4
5
6
7
8
9
10
function greet(): string {
return "Hello World";
}

function caller() {
var msg = greet();
console.log(msg);
}

caller();

编译后的 JavaScript 代码:

1
2
3
4
5
6
7
8
function greet() {
return "Hello World";
}
function caller() {
var msg = greet();
console.log(msg);
}
caller();

带参数函数

在调用函数时,可以向其传递值,这些值被称为参数。多个参数用逗号分隔。语法:

1
2
3
function func_name(param1: datatype, param2: datatype) {
// 执行代码
}
  • param1param2 为参数名
  • datatype 为参数类型

示例:

1
2
3
4
5
function add(x: number, y: number): number {
return x + y;
}

console.log(add(1, 2));

输出:

1
3

可选参数和默认参数

可选参数

如果我们定义了参数,则必须传入这些参数,除非将这些参数设置为可选。可选参数使用问号 ? 标识。

错误示例:

1
2
3
4
5
6
7
8
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}

// 错误:缺少参数
buildName("Bob");
// 错误:参数过多
buildName("Bob", "Adams", "Sr.");

正确示例,将 lastName 设为可选:

1
2
3
4
5
6
7
8
9
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + " " + lastName;
}
return firstName;
}

console.log(buildName("Bob"));
console.log(buildName("Bob", "Adams"));

规则:可选参数必须跟在必需参数后面。如果都是可选参数则无顺序限制。

默认参数

语法:

1
2
3
function function_name(param1: type, param2: type = default_value) {
// 执行代码
}

注意:参数不能同时设置为可选和默认。

示例:

1
2
3
4
5
6
7
function calculate_discount(price: number, rate: number = 0.50) {
var discount = price * rate;
console.log("计算结果: " + discount);
}

calculate_discount(1000);
calculate_discount(1000, 0.30);

编译后的 JavaScript 中默认值通过判断实现:

1
2
3
4
5
function calculate_discount(price, rate) {
if (rate === void 0) { rate = 0.50; }
var discount = price * rate;
console.log("计算结果: " + discount);
}

输出:

1
2
计算结果: 500
计算结果: 300

剩余参数

剩余参数允许我们将一个不确定数量的参数作为一个数组传入。它以 ... 为前缀,是最后一个命名参数,由剩余参数组成的数组。

示例 1:

1
2
3
4
5
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}

console.log(buildName("Joseph", "Samuel", "Lucas", "MacKinzie"));

示例 2:

1
2
3
4
5
6
7
8
9
10
11
function addNumbers(...nums: number[]) {
var i;
var sum = 0;
for (i = 0; i < nums.length; i++) {
sum = sum + nums[i];
}
console.log("和为:", sum);
}

addNumbers(1, 2, 3);
addNumbers(10, 10, 10, 10, 10);

编译后的 JavaScript 使用 arguments 对象收集参数:

1
2
3
4
5
6
7
8
9
10
11
12
function addNumbers() {
var nums = [];
for (var _i = 0; _i < arguments.length; _i++) {
nums[_i] = arguments[_i];
}
var i;
var sum = 0;
for (i = 0; i < nums.length; i++) {
sum = sum + nums[i];
}
console.log("和为:", sum);
}

输出:

1
2
和为: 6
和为: 50

匿名函数

匿名函数是一个没有函数名的函数。它在程序运行时动态声明,可以赋值给变量形成函数表达式。语法:

1
2
3
var res = function ([arguments]) {
// 执行代码
};

不带参数示例:

1
2
3
4
5
var msg = function () {
return "hello world";
};

console.log(msg());

输出:

1
hello world

带参数示例:

1
2
3
4
5
var res = function (a: number, b: number) {
return a * b;
};

console.log(res(12, 2));

输出:

1
24

匿名函数自调用

在函数后使用 () 即可立即调用:

1
2
3
4
(function () {
var x = "Hello!!";
console.log(x);
})();

输出:

1
Hello!!

构造函数

TypeScript 支持使用 JavaScript 内置构造函数 Function() 定义函数。语法:

1
var res = new Function([arg1[, arg2[, ...argN]]], functionBody);

参数说明:

  • arg1arg2、… argN 为参数列表
  • functionBody 为包含函数定义的 JavaScript 语句字符串

示例:

1
2
3
4
var myFunction = new Function("a", "b", "return a * b");
var x = myFunction(4, 3);

console.log(x);

输出:

1
12

递归函数

递归函数即在函数内调用函数本身。示例,计算阶乘:

1
2
3
4
5
6
7
8
9
function factorial(number) {
if (number <= 0) {
return 1; // 停止条件
} else {
return number * factorial(number - 1); // 调用自身
}
}

console.log(factorial(6));

输出:

1
720

函数与 this

在 JavaScript 中,普通函数的 this 取决于函数如何调用,而不是如何定义。TypeScript 同样存在该问题,通常通过显式声明 this 类型或使用箭头函数来解决。

下例中,普通函数作为回调传给 setTimeout,调用时 this 指向 window(严格模式下为 undefined),导致 this.name 取不到预期值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person1 {
name: string;
constructor(name: string) {
this.name = name;
}
greetLater() {
setTimeout(function () {
console.log("普通函数: " + this.name);
}, 100);
}
}

var p1 = new Person1("Alice");
p1.greetLater();

输出:

1
普通函数: undefined

将回调换成箭头函数后,this 会指向定义时的上下文,从而正确访问实例属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person2 {
name: string;
constructor(name: string) {
this.name = name;
}
greetLater() {
setTimeout(() => {
console.log("箭头函数: " + this.name);
}, 100);
}
}

var p2 = new Person2("Bob");
p2.greetLater();

输出:

1
箭头函数: Bob

普通函数的 this 在调用时确定,箭头函数的 this 在定义时确定。

函数重载

函数重载(Function Overloading)允许为一个函数定义多个签名,编译器会根据传入的参数类型选择正确的实现。重载是方法名字相同,而参数不同,返回类型可以相同也可以不同,每种重载方法必须有独一无二的参数类型列表。

函数重载的工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
函数重载签名(声明)
// 签名 1
function add(a: number, b: number): number;
// 签名 2
function add(a: string, b: string): string;

编译器

匹配

函数实现(实际代码)
function add(a: any, b: any): any {
return a + b;
}
必须兼容所有签名

调用时类型推断
add(1, 2) → number
add("a", "b") → string
add(true, false) → any

重载的几种形式

参数类型不同:

1
2
function disp(string): void;
function disp(number): void;

参数数量不同:

1
2
function disp(n1: number): void;
function disp(x: number, y: number): void;

参数类型顺序不同:

1
2
function disp(n1: number, s1: string): void;
function disp(s: string, n: number): void;

说明:参数类型不同时类型应设为 any,参数数量不同时可将多余参数设为可选。

基本语法

先声明多个函数签名,然后实现一个统一函数。实例:

1
2
3
4
5
6
7
8
9
10
11
// 函数重载签名
function add(a: number, b: number): number;
function add(a: string, b: string): string;

// 实现
function add(a: any, b: any): any {
return a + b;
}

console.log("数字相加: " + add(1, 2));
console.log("字符串相加: " + add("Hello, ", "World"));

输出:

1
2
数字相加: 3
字符串相加: Hello, World

参数类型与数量都不同的重载实例:

1
2
3
4
5
6
7
8
9
10
function disp(s1: string): void;
function disp(n1: number, s1: string): void;

function disp(x: any, y?: any): void {
console.log(x);
console.log(y);
}

disp("abc");
disp(1, "xyz");

输出:

1
2
3
4
abc
undefined
1
xyz

多参数重载

可以定义多个参数的不同组合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 多种重载签名
function greet(name: string): string;
function greet(name: string, greeting: string): string;

// 实现
function greet(name: any, greeting?: any): any {
if (greeting) {
return greeting + ", " + name + "!";
}
return "Hello, " + name + "!";
}

console.log(greet("Alice"));
console.log(greet("Bob", "Hi"));

输出:

1
2
Hello, Alice!
Hi, Bob

方法重载

类中的方法也可以使用重载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Calculator {
// 重载签名
add(a: number, b: number): number;
add(a: string, b: string): string;
add(a: number, b: string): string;
add(a: any, b: any): any {
return a + b;
}
}

var calc = new Calculator();
console.log("数字: " + calc.add(1, 2));
console.log("字符串: " + calc.add("Hello", "World"));
console.log("混合: " + calc.add(5, " apples"));

输出:

1
2
3
数字: 3
字符串: HelloWorld
混合: 5 apples

构造函数重载

构造函数同样可以重载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class User {
name: string;
age: number;

// 构造函数重载
constructor(name: string);
constructor(name: string, age: number);
constructor(name: any, age?: any) {
this.name = name;
this.age = age || 0;
}
}

var user1 = new User("Alice");
var user2 = new User("Bob", 25);

console.log("用户1: " + JSON.stringify(user1));
console.log("用户2: " + JSON.stringify(user2));

输出:

1
2
用户1: {"name":"Alice","age":0}
用户2: {"name":"Bob","age":25}

重载与联合类型

使用重载而不是联合类型可以获得更精确的类型推断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 推荐:使用重载
function process(value: number): number;
function process(value: string): string;
function process(value: any): any {
if (typeof value === "number") {
return value * 2;
}
return value.toUpperCase();
}

// TypeScript 知道返回类型
var numResult: number = process(10); // number
var strResult: string = process("hello"); // string

console.log("数字结果: " + numResult);
console.log("字符串结果: " + strResult);

输出:

1
2
数字结果: 20
字符串结果: HELLO

重载注意事项

  • 重载签名必须放在实现签名之前
  • 实现签名必须兼容所有重载签名
  • 重载签名只是类型声明,不生成实际代码

定义函数重载需要定义重载签名和一个实现签名。重载签名定义形参和返回类型但没有函数体,一个函数可以有多个重载签名(不可调用),最终由实现签名提供具体逻辑。

箭头函数

箭头函数(Lambda 函数)表达式的语法比函数表达式更短,并且不绑定自己的 thisargumentssupernew.target。它非常适合用来简化回调与保留外层 this 上下文。

箭头函数语法

箭头函数有单行语句与代码块两种形式:

1
2
3
4
5
6
7
// 单行语句
([param1, param2, ...paramN]) => statement;

// 代码块
([param1, param2, ...paramN]) => {
// 代码块
};

三种常见写法:

  • 多参数:(a, b) => a + b
  • 单参数:n => n * 2
  • 无参数:() => 42

当只有单个参数时,括号可以省略;但没有参数或多于一个参数时,必须使用括号。

箭头函数基础

对比传统写法与箭头函数写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 传统函数表达式
var add1 = function (a: number, b: number): number {
return a + b;
};

// 箭头函数
var add2 = (a: number, b: number): number => a + b;

// 单个参数,括号可省略
var double = (n: number): number => n * 2;

// 无参数使用空括号
var getRandom = (): number => Math.random();

console.log("add1: " + add1(1, 2));
console.log("add2: " + add2(3, 4));
console.log("double: " + double(5));
console.log("random: " + getRandom());

输出示例:

1
2
3
4
add1: 3
add2: 7
double: 10
random: 0.xx

单行语句形式:

1
2
var foo = (x: number) => 10 + x;
console.log(foo(100));

输出:

1
110

代码块形式:

1
2
3
4
5
var foo = (x: number) => {
x = 10 + x;
console.log(x);
};
foo(100);

输出:

1
110

不指定参数类型(由函数内推断):

1
2
3
4
5
6
7
8
9
10
var func = (x) => {
if (typeof x == "number") {
console.log(x + " 是一个数字");
} else if (typeof x == "string") {
console.log(x + " 是一个字符串");
}
};

func(12);
func("Tom");

输出:

1
2
12 是一个数字
Tom 是一个字符串

单个参数时括号可选:

1
2
3
4
var display = (x) => {
console.log("输出为 " + x);
};
display(12);

输出:

1
输出为 12

无参数时使用空括号:

1
2
3
4
var disp = () => {
console.log("Function invoked");
};
disp();

输出:

1
Function invoked

箭头函数与 this

箭头函数最大的价值在于它不会创建自己的 this,而是捕获定义时所在上下文的 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Person1 {
name: string;
constructor(name: string) {
this.name = name;
}
greetLater() {
setTimeout(function () {
console.log("普通函数: " + this.name);
}, 100);
}
}

class Person2 {
name: string;
constructor(name: string) {
this.name = name;
}
greetLater() {
setTimeout(() => {
console.log("箭头函数: " + this.name);
}, 100);
}
}

var p1 = new Person1("Alice");
var p2 = new Person2("Bob");
p1.greetLater();
p2.greetLater();

输出:

1
2
普通函数: undefined
箭头函数: Bob

普通函数的 this 在调用时确定,箭头函数的 this 在定义时确定。

类中的箭头函数

在类中可以把方法写成箭头函数属性,这样 this 始终绑定到实例,便于把方法作为回调传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Counter {
count = 0;

// 箭头函数属性:this 始终绑定到实例
increment = () => {
this.count++;
console.log("当前计数: " + this.count);
};

// 普通方法
decrement() {
this.count--;
console.log("当前计数: " + this.count);
}
}

var counter = new Counter();
counter.increment();
counter.increment();
counter.decrement();

输出:

1
2
3
当前计数: 1
当前计数: 2
当前计数: 1

箭头函数属性会在每个实例中创建新函数,可能增加内存开销。

回调函数中的 this

在数组方法的回调中使用箭头函数,可以正确捕获外层的 this

1
2
3
4
5
6
7
8
9
10
11
var handler = {
name: "Handler",
numbers: [1, 2, 3],
processAll() {
this.numbers.forEach((n) => {
console.log(this.name + ": " + n);
});
}
};

handler.processAll();

输出:

1
2
3
Handler: 1
Handler: 2
Handler: 3

在类的回调方法、数组方法的回调、事件处理函数中,优先使用箭头函数以避免 this 问题。

箭头函数的类型

TypeScript 使用 => 语法为箭头函数标注类型:

1
2
var add: (a: number, b: number) => number = (a, b) => a + b;
console.log("加法: " + add(2, 3));

也可以通过接口定义函数类型:

1
2
3
4
5
6
interface MathOperation {
(a: number, b: number): number;
}

var multiply: MathOperation = (a, b) => a * b;
console.log("乘法: " + multiply(4, 5));

输出:

1
2
加法: 5
乘法: 20

注意:箭头函数类型的语法是 (params) => returnType,不是传统的 (params): returnType

何时使用箭头函数

推荐在以下场景使用箭头函数:

  1. 需要保持 this 上下文时:如回调函数、事件处理、数组方法
  2. 简单的一行函数:如 mapfilterreduce 的回调
  3. 类方法需要传递时:作为回调传递给其他函数

如果需要动态 this(例如事件处理函数中需要使用事件目标),则应使用普通函数。需要 this 绑定时用箭头函数,需要动态 this 时用普通函数。

箭头函数注意事项

  • 不绑定 arguments:箭头函数没有自己的 arguments 对象
  • 不能用作构造函数:不能使用 new 调用
  • 不能用作方法:在对象字面量中使用时,this 可能与预期不符
  • 适合回调:在需要保留 this 上下文的回调中优先使用

总结

  • 函数是 TypeScript 程序的基本构建块,可通过参数类型、返回值类型、可选参数、默认参数、剩余参数等机制实现类型安全的函数定义
  • 函数重载允许为同一个函数定义多个签名,编译器根据参数类型选择匹配的实现,相比联合类型能提供更精确的返回类型推断
  • 箭头函数语法简洁,且不绑定自己的 this,在回调、事件处理、数组方法等场景中能有效避免 this 丢失问题;需要动态 this 时仍应使用普通函数