📚 TypeScript 教程系列
入门与配置 基础类型与变量声明 函数 (本文)流程控制与运算符 集合类型 异步编程与错误处理 接口与类 泛型与类型组合 高级类型 模块、装饰器与工程化 ⚠️ 来源声明 :本文内容参考自 菜鸟教程 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 ();
输出:
函数返回值 通过 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 ) { }
param1、param2 为参数名datatype 为参数类型示例:
1 2 3 4 5 function add (x : number , y : number ): number { return x + y; }console .log (add (1 , 2 ));
输出:
可选参数和默认参数 可选参数 如果我们定义了参数,则必须传入这些参数,除非将这些参数设置为可选。可选参数使用问号 ? 标识。
错误示例:
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:
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 3 var res = function ([arguments ] ) { };
不带参数示例:
1 2 3 4 5 var msg = function ( ) { return "hello world" ; };console .log (msg ());
输出:
带参数示例:
1 2 3 4 5 var res = function (a : number , b : number ) { return a * b; };console .log (res (12 , 2 ));
输出:
匿名函数自调用 在函数后使用 () 即可立即调用:
1 2 3 4 (function ( ) { var x = "Hello!!" ; console .log (x); })();
输出:
构造函数 TypeScript 支持使用 JavaScript 内置构造函数 Function() 定义函数。语法:
1 var res = new Function ([arg1[, arg2[, ...argN]]], functionBody);
参数说明:
arg1、arg2、… 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 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 ));
输出:
函数与 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 ();
输出:
将回调换成箭头函数后,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 ();
输出:
普通函数的 this 在调用时确定,箭头函数的 this 在定义时确定。
函数重载 函数重载(Function Overloading)允许为一个函数定义多个签名,编译器会根据传入的参数类型选择正确的实现。重载是方法名字相同,而参数不同,返回类型可以相同也可以不同,每种重载方法必须有独一无二的参数类型列表。
函数重载的工作原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 函数重载签名(声明) function add( a: number, b: number) : number; 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 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 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 (); }var numResult : number = process (10 ); var strResult : string = process ("hello" ); console .log ("数字结果: " + numResult);console .log ("字符串结果: " + strResult);
输出:
重载注意事项 重载签名必须放在实现签名之前 实现签名必须兼容所有重载签名 重载签名只是类型声明,不生成实际代码 定义函数重载需要定义重载签名和一个实现签名。重载签名定义形参和返回类型但没有函数体,一个函数可以有多个重载签名(不可调用),最终由实现签名提供具体逻辑。
箭头函数 箭头函数(Lambda 函数)表达式的语法比函数表达式更短,并且不绑定自己的 this、arguments、super 或 new.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 2 3 4 5 var foo = (x : number ) => { x = 10 + x; console .log (x); };foo (100 );
输出:
不指定参数类型(由函数内推断):
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 3 4 var display = (x ) => { console .log ("输出为 " + x); };display (12 );
输出:
无参数时使用空括号:
1 2 3 4 var disp = ( ) => { console .log ("Function invoked" ); };disp ();
输出:
箭头函数与 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 ; increment = () => { this .count ++; console .log ("当前计数: " + this .count ); }; decrement ( ) { this .count --; console .log ("当前计数: " + this .count ); } }var counter = new Counter (); counter.increment (); counter.increment (); counter.decrement ();
输出:
箭头函数属性会在每个实例中创建新函数,可能增加内存开销。
回调函数中的 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 ));
输出:
注意:箭头函数类型的语法是 (params) => returnType,不是传统的 (params): returnType。
何时使用箭头函数 推荐在以下场景使用箭头函数:
需要保持 this 上下文时:如回调函数、事件处理、数组方法 简单的一行函数:如 map、filter、reduce 的回调 类方法需要传递时:作为回调传递给其他函数 如果需要动态 this(例如事件处理函数中需要使用事件目标),则应使用普通函数。需要 this 绑定时用箭头函数,需要动态 this 时用普通函数。
箭头函数注意事项 不绑定 arguments:箭头函数没有自己的 arguments 对象 不能用作构造函数:不能使用 new 调用 不能用作方法:在对象字面量中使用时,this 可能与预期不符 适合回调:在需要保留 this 上下文的回调中优先使用 总结 函数是 TypeScript 程序的基本构建块,可通过参数类型、返回值类型、可选参数、默认参数、剩余参数等机制实现类型安全的函数定义 函数重载允许为同一个函数定义多个签名,编译器根据参数类型选择匹配的实现,相比联合类型能提供更精确的返回类型推断 箭头函数语法简洁,且不绑定自己的 this,在回调、事件处理、数组方法等场景中能有效避免 this 丢失问题;需要动态 this 时仍应使用普通函数