函数签名与函数重载
函数签名
也叫类型签名,或方法签名,定义了函数或方法的输入与输出
函数定义
- 复习一下 javaScript 主要的 2 种函数定义方式
//函数声明
function add(x, y) {
return x + y;
}
//函数表达式: 把匿名函数赋值给变量
let myAdd = function(x, y) { return x + y; };
- 为 JS 函数的入参和返回值加上类型,其实已经对 JS 函数进行了类型约束
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number { return x + y; };
函数类型
- 一个完整的函数类型:只包含两部分,参数类型和返回值类型
- 参数类型的名字和函数参数的名字可以不一致,写成一致是为了增加该函数类型的可读性。
- 一边指定了类型但是另一边没有类型的话,TypeScript编译器会通过类型推断自动识别出类型。
let myAdd = (x: number, y: number) => number = function(x: number, y: number): number { return x + y; };
类型接口 Type Interface
- Interface 还可以用来规范函数的形状
- Interface 里面需要列出参数列表返回值类型的函数定义。写法如下:
- 定义了一个函数接口
- 约定接受的参数和返回值的类型
- 使用函数表达式来定义这种形状的函数
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc = function(source: string, subString: string):boolean {
let result = source.search(subString);
return result > -1;
}
// 允许不指定类型,TypeScrit 会进行推断
let mySearch: SearchFunc = function (src, sub) {
let result = src.search(sub);
return result > -1;
}
对于只有一个只有一个函数的接口,可以使用 type 来定义一个函数的类型:
类型别名 Type Aliases
- 类型别名会给一个类型起个新名字。
- 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型
- 起别名不会新建一个类型 - 它创建了一个新名字来引用那个类型。
- 给原始类型起别名通常没什么用,尽管可以做为文档的一种形式使用。
function greeter(fn: (a: string) => void) {
fn("Hello, World");
}
function printToConsole(s: string) {
console.log(s);
}
greeter(printToConsole);
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
// ...
}
interface 和 type 的区别
- type 可以而 interface 不行
- type 可以声明基本类型别名,联合类型,元组等类型
// 基本类型别名
type Name = string
// 联合类型
interface Dog {
wong();
}
interface Cat {
miao();
}
type Pet = Dog | Cat
// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]
- type 语句中还可以使用 typeof 获取实例的 类型进行赋值
// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div
- interface 可以而 type 不行
interface User {
name: string
age: number
}
interface User {
sex: string
}
/*
User 接口为 {
name: string
age: number
sex: string
}
*/
参数
可选参数
- 传递给一个函数的参数个数必须与函数期望的参数个数一致,编译器检查用户是否为每个参数都传入了值。
- 对于 JS 中经常遇到的一个函数调用时,对于声明时的入参可传可不传。我们可以使用 可选参数
可选参数必须跟在必须参数后面
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right
默认参数
- 默认参数也是可选参数
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result4 = buildName("Bob", "Adams"); // ah, just right
剩余参数
- 将剩余的参数收敛到一个变量(编译器创建参数数组)里。并可对该变量进行访问
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
返回值
函数的返回值类型除了返回原始类型之外,一般还会经常返回 any,nerver,void
any
never
never
类型表示的是那些永不存在值的类型。- 没有类型是
never
的子类型或可以赋值给never
类型,即使any
也不可以赋值给never
// 返回 错误
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
void
- 某种程度上来说,
void
类型像是与any
类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是void
:
function warnUser(): void {
console.log("This is my warning message");
}
- 声明一个
void
类型的变量没有什么大用,因为你只能为它赋予undefined
和null
:
let unusable: void = undefined;
函数重载
- 函数重载: **函数项名称相同 **但输入输出类型或个数不同的子程序,它可以简单地称为一个单独功能可以执行多项任务的能力。
- TypeScript 的函数重载: 为同一个函数提供多个函数类型定义来进行函数重载,目的是重载的
pickCard
函数在调用的时候会进行正确的类型检查。 - js 因为是动态类型,本身不需要支持重载,直接对参数进行类型判断即可,但是ts为了保证类型安全,支持了函数签名的类型重载,即多个overload signatures和一个implementation signatures
// 重载签名
function add(x:string,y:string):string;
function add(x:number, y:number):number;
//实现签名 对外不可见
function add(x:string|number, y: number|string): number | string{
if(typeof x === 'string'){
return x + ',' + y;
}else {
return x.toFixed() + (y as number).toFixed();
// 很不幸,ts暂时不支持对函数重载后续参数的narrowing操作,如这里对x做了type narrowing但是对y没有做narrowing,需要手动的y做type assert操作
见https://github.com/Microsoft/TypeScript/issues/22609
}
}
let x = add(1,2) // string
let y = add(2,3) // number
实现重载有几个注意点
- 因为implementation signatures对外是不可见的,当我们实现重载时,通常需要定义两个以上的overload signatures
- implementation signature和 overload signature必须兼容,否则会 type check error 如下implementation 和 overload 不兼容
- overload signature的类型不会合并,只能resolve到一个
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
return x.length;
}
len(""); // OK
len([0]); // OK
let t = Math.random() > 0.5 ? "hello" : [0]
len(t); // 这里的t是string|number[]但是仍然check error
因此这里就不要用重载了,直接用union type吧。
function len(x: any[] | string) {
return x.length;
}
let t = Math.random() > 0.5 ? "hello" : [0]
len(t); // 没问题了
函数类型的兼容性
1、参数个数 目标函数的参数个数一定要多于源函数的参数个数。 2、参数类型 3、返回值类型 目标函数的返回值类型必须与源函数的返回值类型相同,或者是其子类型。