[TypeScript学习笔记-05]作为一等成员的函数

函数是任何应用程序的基本构建块,无论是本地函数、从其他模块导入的函数,还是类中的方法。它们也是值,就像其他值一样,TypeScript 提供了多种方式来描述函数的调用方式。让我们来学习如何编写描述函数的类型。

1. 函数类型表达式

  • 可以使用类似于C#中Lambda表达式的箭头符号
typescript 复制代码
function greeter(fn: (a: string) => void) {
  fn("Hello, World");
}
 
function printToConsole(s: string) {
  console.log(s);
}
 
greeter(printToConsole);
  • 为函数类型定义别名:
typescript 复制代码
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
  // ...
}

2. 调用签名(Call Signatures)

  • 调用签名可以为函数定义额外成员
typescript 复制代码
type DescribableFunction = {
  description: string;          //额外成员
  (someArg: number): boolean;   //函数体
};

function doSomething(fn: DescribableFunction) {
  console.log(fn.description + " returned " + fn(6)); // 成员读取
}
 
function myFunc(someArg: number) {
  return someArg > 3;
}
myFunc.description = "default description"; //成员赋值
 
doSomething(myFunc);
  • 使用 new 操作符添加构造函数
typescript 复制代码
type SomeConstructor = {
  new (s: string): SomeObject;
};

function fn(ctor: SomeConstructor) {
  return new ctor("hello");
}
  • 接口形式定义:
typescript 复制代码
interface CallOrConstruct {
  (n?: number): string;     // 常规函数
  new (s: string): Date;    // 构造函数
}
 
function fn(ctor: CallOrConstruct) {  
  console.log(ctor(10));        //由于传入参数类型为number,会选择常规函数
  console.log(new ctor("10"));  //由于传入参数类型为string,会选择构造函数
}
 
fn(Date);

3. 泛型(Generics)

typescript 复制代码
function firstElement<Type>(arr: Type[]): Type | undefined {
  return arr[0];
}
  • 使用 extends 关键字为泛型参数定义约束:
typescript 复制代码
function longest<Type extends { length: number }>(a: Type, b: Type) {
  if (a.length >= b.length) {
    return a;
  } else {
    return b;
  }
}

4. 可缺省参数(Optional Parameters)

  • 使用 操作符 (参数值默认为 undefined ):
typescript 复制代码
function f(x?: number) {
  return x;
}
f();    // undefined
f(10);  // 10
  • 显式指定默认值:
typescript 复制代码
function f(x: number = 10) {
  // ...
}
f();    // 10
f(10); ·// 10
  • 回调函数中使用可缺省参数的问题:
typescript 复制代码
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i);
  }
}

myForEach([1,2,3], (x) => console.log(x));

5. 函数重载(Overload)

在 TypeScript 中,我们可以通过编写重载签名 来指定一个函数可以以不同的方式调用。唯一提供实现的函数被称为实现签名(Implementation Signature) ,其余为重载签名(Overload Signatures)

typescript 复制代码
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  if (d !== undefined && y !== undefined) {
    return new Date(y, mOrTimestamp, d);
  } else {
    return new Date(mOrTimestamp);
  }
}
const d1 = makeDate(12345678);  //OK
const d2 = makeDate(5, 5, 5);   //OK
const d3 = makeDate(1, 3);      //失败
  • 只有重载签名可以被调用:
typescript 复制代码
function fn(x: string): void;
function fn() {
  // ...
}
fn(); //调用失败
  • 实现签名必须兼容重载签名:
typescript 复制代码
function fn(x: boolean): void;
function fn(x: string): void;

//输入参数不兼容
function fn(x: boolean) {} 

function fn(x: string): string;
function fn(x: number): boolean;

//返回类型不兼容
function fn(x: string | number) {
  return "oops";
}

function len(s: string): number;
function len(arr: any[]): number;

//兼容
function len(x: any) {
  return x.length;
}

6. 函数中声明this

TypeScript 会通过代码流分析来推断函数中的 this 应该是什么。

typescript 复制代码
const user = {
  id: 123,
 
  admin: false,
  becomeAdmin: function () {
    this.admin = true;
  },
};
  • 有很多情况下,你需要对它所代表的对象进行更多控制
typescript 复制代码
interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

: JavaScript 规范规定不能有名为 this 的参数,因此 TypeScript 利用这个语法空间,允许你在函数体中声明 this 的类型。

  • 不能使用箭头函数 ,因为它会此时会捕捉全局的上下文作为 this
typescript 复制代码
interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(() => this.admin); // 错误

7. 特殊的类型的使用

  • void:会根据函数是否返回有效对象自行推断:
typescript 复制代码
function noop() {
  return;
}

: 在 JavaScript 中,不返回任何值的函数会隐式返回 undefined。然而,在 TypeScript 中,voidundefined 并非同一概念;

  • object 指的是任何非基本类型 (字符串、数字、bigint、布尔值、符号、null 或 undefined)的值。这与空对象类型 {} 不同,也与全局类型 Object 不同;

: 在 JavaScript 中,函数值是对象,内在的含义是它们拥有属性,原型链中包含 Object.prototype,是 Object 的实例,可以调用 Object.keys 等方法。因此,在 TypeScript 中,函数类型也被视为对象

  • unknown:未知类型表示任意值。这类似于 any 类型,但更安全 ,因为对未知值进行任何操作都是不合法:
typescript 复制代码
function f1(a: any) {
  a.b(); // OK
}
function f2(a: unknown) {
  a.b(); // Fail: 'a' is of type 'unknown'.
}

在描述函数类型 时非常有用,可以描述接受任何值的函数,而无需在函数体中包含任何值。

typescript 复制代码
function safeParse(s: string): unknown {
  return JSON.parse(s);
}
 
// Need to be careful with 'obj'!
const obj = safeParse(someRandomString);
  • never: 表示永远不会被观察到的值。在返回类型中,这意味着函数会抛出异常或终止程序执行。
typescript 复制代码
function fail(msg: string): never {
  throw new Error(msg);
}
  • Function: 描述了 JavaScript 中所有函数值都具有的属性,例如 bindcallapply 等。它还有一个特殊属性,即 Function 类型的值始终可以被调用 ;这些调用会返回任意值
typescript 复制代码
function doSomething(f: Function) {
  return f(1, 2, 3);
}

8. 剩余参数(Rest Parameters and Arguments)

  • 除了使用可选参数或重载来创建可以接受各种固定参数数量的函数之外,我们还可以使用剩余参数来定义接受无限数量参数 的函数。类似于C#的 params 关键字。
typescript 复制代码
function multiply(n: number, ...m: number[]) {
  return m.map((x) => n * x);
}
// 'a' gets value [10, 20, 30, 40]
const a = multiply(10, 1, 2, 3, 4);
  • 参数的类型注解隐式为 any[] 而不是 any,并且任何给定的类型注解都必须是 Array<T>T[] 的形式,或者是一个元组类型。

  • 我们可以使用扩展语法从可迭代对象(例如数组)中提供可变数量的参数。相当于Python的拆包

typescript 复制代码
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.push(...arr2);
  • TypeScript 并不假定数组是不可变的,所以不能通过上述方式作为具有固定参数函数的输入:
typescript 复制代码
const args = [8, 5];
const angle = Math.atan2(...args); // 错误
  • 解决方案: 通过as const将其固定下来:
typescript 复制代码
const args = [8, 5] as const;
const angle = Math.atan2(...args); // 正确

9. 参数解构(Parameter Destructuring)

你可以使用参数解构来方便地将作为参数提供的对象解包到函数体内的一个或多个局部变量中。

typescript 复制代码
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
  console.log(a + b + c);
}

10. 可赋值型(Assignability)

  • 上下文类型设置(返回类型为 void )并不会强制函数不返回任何值:
typescript 复制代码
type voidFunc = () => void;
 
// OK
const f1: voidFunc = () => {
  return true;
}; 

// OK
const f2: voidFunc = () => true;

// OK
const f3: voidFunc = function () {
  return true;
};
  • void 函数的返回值被赋给另一个变量时,它将保持 void 类型:
typescript 复制代码
const v1 = f1(); 
const v2 = f2();
const v3 = f3();
  • 这种行为的存在是因为即使函数返回了一个值,但是接收方可以丢弃,所以如下的代码是合法的:
typescript 复制代码
const src = [1, 2, 3];
const dst = [0];
 
src.forEach((el) => dst.push(el));
  • 字面函数 定义具有 void 返回类型时,该函数不能返回任何内容:
typescript 复制代码
function f2(): void {
  return true; // 错误
}
 
const f3 = function (): void {
  return true; // 错误
};
相关推荐
常宇佳2 小时前
vue3 @代指src路径设置
前端·typescript·vue
常宇佳7 小时前
vue3 $refs使用方法
前端·vue.js·typescript
不好听6131 天前
TypeScript 面试必会:工具类型全家桶,从 keyof 到手写 Partial
面试·typescript
嘟嘟07171 天前
从入口到 CRUD:用 NestJS 的模块化与装饰器思想读懂一个后端应用
后端·typescript·nestjs
嘟嘟07171 天前
TypeScript 工具类型通关:Pick、Omit、Partial、Record 区别与 Omit 等价实现一次讲清
设计模式·面试·typescript
何时梦醒1 天前
TypeScript 工具类型一篇讲透:Pick、Omit、Partial、Exclude、Record、ReturnType、keyof
前端·面试·typescript
小林ixn1 天前
TS 工具类型实战:Pick/Omit/Partial/Record 一次讲透,别再 Omit 反了
typescript·代码规范
苏灿烤鱼1 天前
十个 CLI 坐进一间办公室,协调层靠得住吗?
typescript·github·agent
BreezeJiang1 天前
别再死记 TypeScript 工具类型:看懂 Omit 的类型数据流
typescript