1.函数声明

TypeScript的函数声明形式比JavaScript要复杂一些

1.1 JavaScript函数声明

  • 函数声明语句声明函数:

    function test() {}
    
  • 赋值语句声明函数:

    const test = function(){}
    
  • 声明箭头函数:

    const test = () => {}
    

1.2 TypeScript函数声明

  • 函数声明语句声明函数

  • 赋值语句声明函数

  • 声明箭头函数

  • 接口或类型别名声明函数:

    interface Test {
        (arg: string): void
    }
    
    // type Test = (arg: string): void // 使用类型别名描述
    
    const test: Test = (arg) => {}
    
  • 函数签名声明函数:

    函数签名可以指定更多内容,除了指定函数体外,还可以指定函数属性

    // 声明函数签名
    type Func = {
        description: string;
        (arg: string): void;
    }
    
    // 实现指定函数
    function func(name: string): void {
        console.log(name)
    }
    func.description = 'this is a function'
    
    // 实现函数签名
    const fc: Func = func
    
    fc("Danny")
    
    

2.函数签名

函数签名用来描述函数详细信息,普通形式声明函数不能定义函数属性,使用函数签名可以实现。

2.1 调用签名

调用签名是普通的函数签名,借助类型别名或接口来实现。

2.1.1 包含属性的函数签名

// 声明函数签名
type Func = {
    description: string;
    (arg: string): void;
}
// 实现指定函数
function func(name: string): void {
    console.log(name)
}
func.description = 'this is a function'
// 实现函数签名
const fc: Func = func

// 也可以使用接口
interfacre Func {
    description: string,
    (arg: string): void
}

2.1.2 简单的函数签名

// 类型别名
type Func = (name: string, age: number) => void
const func: Func = (n: string, a: number) => {}

// 类型别名简写
const func: (name: string, age: number) => void = (n: string, a: number) => {}

// 接口
interface Func {
    (name: string, age: number): void
}
const func: Func = (n: string, a: number) => {}

// 接口简写
const func: { (name: string, age: number): void } = (n: string, a: number) => {}

2.2 构造签名

构造签名用于描述类的构造函数, 但构造函数不能通过构造签名声明,也不能实现构造签名。

构造签名的应用场景主要就是“工厂函数”,函数接收一个类,返回该类实例化的对象。

// 通过接口来描述构造签名
interface Constructor {
    new (name: string, age: number): { name: string, age: number }
}

// 构造签名描述的构造函数所在的类
class Person {
    constructor(public name: string, public age: number) {}
}

// 工厂函数
function factory(constructor: Constructor, name: string, age: number): Person {
    return new constructor(name, age)
}

// 构造函数通过类名调用,所以将Person类传递过去
console.log(factory(Person, 'Danny', 21))

↓↓或者也可以指定类实现这个构造签名,但是没有什么实际意义

interface Constructor {
    new (name: string, age: number): { name: string, age: number };
}

// 通过类型注释来说明类实现了构造签名
const Person: Constructor = class {
    constructor(public name: string, public age: number) {}
}

2.3 重载签名

重载签名用于函数重载。不像是调用签名和构造签名都是由箭头函数或其语法糖来实现,重载签名只能由普通函数实现

function func(name: string, age: number): { name: string, age: number } // 没有函数体的普通函数就是重载签名

类中的构造函数不允许指定返回类型,所以和普通函数稍有区别

class Stu {
    public name: string
    public age: number
    
    constructor(name: string, age: number) // 不指定返回类型
    constructor() // 不指定返回类型
    constructor(name?: string, age?: number) {
        if(name) {
            this.name = name
            this.age = age !
        } else {
            this.name = "none"
            this.age = 0
        }
        return this
    }
}

3.函数可选参数

3.1 回调函数的可选参数

  • 回调函数:

    在JavaScript和TypeScript中,回调函数的特征可以简要描述为:函数作为另一个函数的参数。

  • 不完全的静态类型检查:

    TypeScript并不是完全做静态类型检查,在对于回调函数类型的检查中,静态类型检查这一标准变得宽泛。回调函数的实参个数可以小于形参可数。

    • 在C++中对于形参实参个数的检查是严格的:

      #include <iostream>
      
      using namespace std;
      
      void sayHello() {
        cout << "Hello, world!" << std::endl;
      }
      
      void callFunction(void (*functionPtr)(int a, int b)) {
        functionPtr(1, 2);
      }
      
      int main() {
        callFunction(&sayHello); // 报错,sayHello函数形参个数为0,callFunction中的形参函数要求2个参数
        return 0;
      }
      
    • 在TypeScript中,回调函数的实参个数可以小于形参个数:

      因为在调用回调函数时,相当于传了多余的参数,这些参数会被忽略,一定不会产生错误。如果实参个数多于形参个数,例如下面第三个例子,那么在调用回调函数时,一定会少传参数,一定会报错。

      const func = (arg: (name: string, age: number) => void): void => {}
      
      func(() => {}) // 合法的
      
      func((name: number) => {}) // 非法的
      
      func((name: string, age: number, gender: string) => {}) // 非法的
      
  • 回调函数的形参不需要写可选参数:

    上文提到了回调函数的实参可以小于等于其实参,这样的话没有必要在回调函数中写可选参数。

    // 简易实现数组的过滤函数的部分功能
    const filter = <T>(ar: Array<T>, func: (el: T, idx: number, ar: Array<T>) => boolean): Array<T> => ar.filter(func)
    
    const ar = [1, 2, 3, 4]
    
    console.log(filter(ar, el => !!(el % 2)))
    

    下面的func中的el,idx,ar不需要再类型注释前添加问号。

    const filter = <T>(ar: Array<T>, func: (el?: T, idx?: number, ar?: Array<T>) => boolean): Array<T> => ar.filter(func) // 没有必要的操作
    

4.函数剩余参数

4.1 剩余形参

剩余形参比较简单,注意类型注释一定是数组,因为在函数中使用剩余变量的时候是数组类型。

// 求和函数
function add(arg1: number, arg2: number, ...args: number[]): number { // 剩余形参类型注释为数组
    return arg1 + arg2 + args.reduce((arg1, arg2) => arg1 + arg2, 0)
}
console.log(add(1, 2, 3, 4, 5, 6))

当不对剩余形参注释时,默认是any类型的数组。

// es5对于call的声明
call(this: Function, thisArg: any, ...argArray: any[]): any // 剩余形参为any类型数组

4.2 剩余实参

TypeScript类型推断会默认将数组推测为长度未知的数组,这样展开后传给函数的实参的个数是未知的,会导致报错。

function add(a: number, b: number) {
    return a + b
}
const ar = [1, 2]
console.log(add(...ar)) // 报错,实参个数和形参个数不匹配

剩余实参要注意转化为不可变类型。

function add(a: number, b: number) {
    return a + b
}
const ar = [1, 2] as const
console.log(add(...ar)) 
  • 类比:

    另一个类似的情景是在函数中使用泛型约束值,也需要强制类型转化。

    // 修改对象length为尽可能小的值
    function setMinLength<T extends { length: number }>(obj: T, minLength: number): T {
        if(obj.length < minLength)
            return obj
        return { length: minLength } as T // 需要强制类型转化为T类型。因为单纯包含length的对象不是T类型,T类型可能含有更多属性
    }
    
  • 回顾:

    在枚举中使用常量枚举不会产生中间结果(反向映射)以提高效率。

    const enum Fruit {
        Apple,
        Orange,
        Melon
    }
    console.log(Fruit) // 非法的,常量枚举不会产生中间结果Fruit
    console.log(Fruit.Apple) // 合法,只能访问枚举值或其关联值
    

5.函数参数解构

函数参数解构非常简单,只要在解构大括号之后进行类型注释即可。

不管解构多花里胡哨,有几层嵌套,是否有重命名,我们只需要关注解构在何处结束,然后在后面添加类型注释即可。

// 累加各科成绩
function add({math: a, biology: b, science: c}: { math: number, biology: number, science: number }): number {
    return a + b + c
}

console.log(add({
    math: 135,
    biology: 74,
    science: 67
}))

// 推荐将类型注释提取成类型别名或接口
interface StuGrade {
    math: number,
    biology: number,
    science: number
}
  • 类比:

    使用解构提取值时我们不需要进行类型注释,此时类型是可以推断的

    const stuGrade = {
        math: 135,
        biology: 74,
        science: 67
    }
    
    const { math: a, biology: b, science: c } = stuGrade
    

6.函数参数this类型注释

TypeScript中函数除了可以对参数类型,返回值类型进行注释,还可以对this指向进行注释。

  • 对this进行类型注释:

    函数对this进行类型注释时,this作为第一个参数。

    interface Stu {
        name: string,
        age: number
    }
    
    interface Person {
        gender: string,
        location: string,
        setStuInfo(this: Stu, name: string, age: number): void // 指定了setStuInfo函数中this必须是Stu类型
    }
    
    const person: Person = {
        gender: 'male',
        location: 'China',
        setStuInfo(name: string, age: number) {
            console.log(this)
        }
    }
    
    person.setStuInfo('Danny', 21) // 报错,函数作为对象方法调用,this指向person对象,不是Stu
    
  • call和apply和bind无视this类型注释:

    person.setStuInfo.call({}, 'Danny', 21) // 使用call调用上述函数,this强制切换为{},不会报错
    

7.函数返回值与void

  • 类型注释声明的返回值为void类型的函数将不限制返回值:

    const func: { (): void } = () => true // 合法,不会报错
    console.log(func()) // 输出true
    
  • 普通声明的返回值为void类型的函数限制返回值:

    const func: () => void = () => true // 报错,返回值不应该为布尔类型 
    

8.函数重载

函数重载就是同一个名称的函数可以实现不同的功能,它的参数和返回值都可以不同。

8.1 C++函数重载

直接声明多个同名函数,只是函数返回值和函数参数可以不同。

#include <bits/stdc++.h>

using namespace std;

int test(int a, int b) {
	return a + b;
}

int test(int a, int b, int c) {
	return a + b + c;
}

int main() {
  cout << test(1, 2) << " " << test(1, 2, 3);
  return 0;
}

8.2 TypeScript函数重载

  • 普通函数重载:

    直接声明多个同名函数是非法的。需要先声明多个函数的重载签名,再声明函数来实现所有的函数。

    // 声明重载函数签名
    function func(name: string, age: number): { name: string, age: number }
    // 声明重载函数签名
    function func(a: number, b: number): number
    // 声明函数,实现所有重载函数签名
    function func(arg1: string | number, arg2: number) {
        if(typeof arg1 === 'string')
            return {
                name: arg1,
                age: arg2
            }
        return arg1 + arg2
    }
    
  • 通过函数签名重载:

    interface Func {
        (a: number, b: number): number,
        (a: number, b: number, c: number): number
    }
    
    const func: Func = (a: number, b: number, c?: number) => c ? a + b + c : a + b
    
  • 构造函数重载

    TypeScript中构造函数不允许指定返回类型,所以重载构造函数时函数签名不包含返回类型。

    // 构造函数签名
    interface StuConstructor {
        new(id: string, name: string, age: number, password: string): { id: string, name: string, age: number, password: string }
        new(): { id: string, name: string, age: number, password: string }
    }
    
    const Stu: StuConstructor = class {
    
        public id: string
        public name: string
        public age: number
        public password: string
    	
        // 构造函数重载
        constructor(id: string, name: string, age: number, password: string)
        constructor()
        // 实现构造函数
        constructor(id?: string, name?: string, age?: number, password?: string) {
            if(id) {
                this.id = id
                this.name = name!
                this.age = age!
                this.password = password!
                return this
            }
            this.id = '000'
            this.name = 'none'
            this.age = 21
            this.password = '12345'
        }
    	
        // Stu赋值为了匿名类,通过Symbol设置该匿名类的名称为Stu
        get [Symbol.toStringTag]() {
            return 'Stu'
        }
    }
    
    const stu = new Stu
    const stuD = new Stu('201900800164', 'Danny', 21, '79707536')
    console.log(stu, stuD)
    

更多推荐