TypeScript 关于函数

TypeScript 关于函数

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

函数类型限制

具名函数和匿名函数

// Named function
function add(x, y) {
    return x + y;
}

// Anonymous function
let myAdd = function(x, y) { return x + y; };

为函数添加类型

function add(x: number, y: number): number {
    return x + y;
}

let myAdd = function(x: number, y: number): number { return x + y; };

一个完整的函数类型
myAdd 是一个函数,对其进行限制,其中 => 前面是参数,=> 后面是返回值。参数接收两个 number 类型的,参数名可以定义为任意参数名。返回值是 number 类型的,值由一个函数获得,该函数入参和返回值都是 number 类型。

// 给出了完整了类型限制
let myAdd: (x: number, y: number) => number =
    function(x: number, y: number): number { return x + y; };

函数类型推断

// 给出了完整的类型限制,不会触发类型推断
let myAdd = function(x: number, y: number): number { return x + y; };

// x,y没有限制类型,但因为返回值是 number 类型的,所以 x,y 自动推断为 number 
let myAdd: (baseValue: number, increment: number) => number =
    function(x, y) { return x + y; };

函数类型表达式

描述函数的最简单方法是使用函数类型表达式。这些类型在语法上类似于箭头函数。

语法 (a: string) => void 的意思是“一个带有一个参数、名为 a、类型为 string、没有返回值的函数”。就像函数声明一样,如果未指定参数类型,则隐式为 any。

❗❗❗请注意,参数名称是必需的。函数类型 (string) => void 的意思是“一个带有名为 string、类型为 any 的参数的函数”!

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) {
  // ...
}

调用签名

在 JavaScript 中,函数除了可调用之外还可以具有属性。但是,函数类型表达式语法不允许声明属性。如果我们想用属性描述可调用的东西,我们可以在对象类型中编写调用签名。

❗❗❗请注意,与函数类型表达式相比,语法略有不同。在参数列表和返回类型之间使用 : 而不是 =>。

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);

构造签名

JavaScript 函数也可以使用 new 运算符调用。TypeScript 将它们称为构造函数,因为它们通常会创建一个新对象。你可以通过在调用签名前添加 new 关键字来编写构造签名。

type SomeConstructor = {
  new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
  return new ctor("hello");
}

一些对象,比如 JavaScript 的 Date 对象,可以在有或没有 new 的情况下调用。你可以任意组合相同类型的调用和构造签名。

interface CallOrConstruct {
  (n?: number): string;
  new (s: string): Date;
}

可选参数和默认参数

只要定义了参数就必须传值,即实参和形参个数要一致,否则报错。

function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr.");  // error, too many parameters
let result3 = buildName("Bob", "Adams");         // ah, just right

如果不一定传某个参数,可以用可选参数接收。可选参数必须放在最后。

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

可以用默认参数去实现可选参数功能。当用户没有传递这个参数或传递的值是undefined 时触发默认参数赋值。

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

与可选参数不同的是,默认参数可以不放在最后,但如果想要触发默认参数赋值,必须传 undefined。

function buildName(firstName = "Will", lastName: string) {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr.");  // error, too many parameters
let result3 = buildName("Bob", "Adams");         // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams");     // okay and returns "Will Adams"
回调中的可选参数

一旦我们了解了可选参数,很容易写出下面的问题代码。

规则:为回调编写函数类型时,切勿编写可选​​参数,除非你打算在不传递该参数的情况下调用该函数

function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i]);
  }
}

myForEach([1, 2, 3], (a) => console.log(a)); // ok
myForEach([1, 2, 3], (a, i) => console.log(a, i)); // ok
myForEach([1, 2, 3], (a, i) => {
  console.log(i.toFixed()); // 报错 'i' is possibly 'undefined'.
});

剩余参数

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

参数解构

详见 ECMAScript 常用语法之解构赋值-函数参数的解构赋值

函数重载

重载签名和实现签名

下面示例前两个没有函数体的 len 是重载签名,一般重载签名至少有两个,不然就够不上重载这一说。最后一个有函数体的 len 叫实现签名。

注意规则❗❗❗

  1. 从外部看不到实现的签名。在编写重载函数时,你应该始终在函数实现之上有两个或多个签名。
  2. 实现签名必须兼容重载签名
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
  return x.length;
}

len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]); // 报错

最后一个调用报错,因为我们可以用字符串或数组调用它。但是,我们不能使用可能是字符串或数组的值来调用它,因为 TypeScript 只能将函数调用解析为单个重载。

对比联合类型

上面重载函数我们可以用联合类型去实现相同的功能。

function len(x: any[] | string) {
  return x.length;
}

尽可能使用联合类型的参数而不是重载

函数中的 this

TypeScript 将通过代码流分析推断函数中的 this 应该是什么,例如:

const user = {
  id: 123,
 
  admin: false,
  becomeAdmin: function () {
    this.admin = true;
  },
};

TypeScript 理解函数 user.becomeAdmin 有一个对应的 this,它是外部对象 user。很多情况下就够用了,但也有很多情况下,你需要更多的控制 this 代表什么对象。JavaScript 规范规定你不能有一个名为 this 的参数,因此 TypeScript 使用该语法空间让你在函数体中声明 this 的类型。

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

这种模式在回调风格的 API 中很常见,其中另一个对象通常控制何时调用你的函数。请注意,你需要使用 function 而不是箭头函数来获得此行为:

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(() => this.admin);
// The containing arrow function captures the global value of 'this'.
// Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.

泛型函数

详见 Typescript 泛型

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值