Typescript-函数签名与函数重载

18,129 阅读6分钟

函数签名与函数重载

函数签名

也叫类型签名,或方法签名,定义了函数或方法的输入与输出

函数定义

  • 复习一下 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类型的变量没有什么大用,因为你只能为它赋予undefinednull
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、返回值类型 目标函数的返回值类型必须与源函数的返回值类型相同,或者是其子类型。