【Typescript入门手册】函数

【Typescript入门手册】函数TypeScript 入门手册 记录了出场率较高的 Ts 概念 旨在帮助大家了解并熟悉 Ts 本系列会持续更新并更正 重点关照大家感兴趣的点 欢迎同学留言交流 在进阶之路上 共勉

【Typescript入门手册】函数

一、函数声明

函数是一等公民,在JavaScript中,有两种常见的定义函数的方式——函数声明和函数表达式,让我们来学习一下如何书写描述函数的类型(types)。

// 函数声明(Function Declaration)
function sum(x, y) { 
    
  return x + y;
}

// 函数表达式(Function Expression)
const mySum = function(x, y) { 
    
  return x + y;
};

最简单描述一个函数的方式是使用函数类型表达式(function type expression)。它的写法有点类似于箭头函数:

function greeter(fn: (a: string) => void) { 
    
  fn("Hello, World");
}
 
function printToConsole(s: string) { 
    
  console.log(s);
}
 
greeter(printToConsole);

语法(a: string) => void表示一个函数有一个名为a,类型是字符串的参数,这个函数并没有返回任何值。

如果一个函数参数的类型并没有明确给出,它会被隐式设置为 any。

注意函数参数的名字是必须的,这种函数类型描述(string) => void,表示的其实是一个函数有一个类型是 any,名为 string 的参数。

当然了,我们也可以使用类型别名(type alias)定义一个函数类型:

type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) { 
    
    // ...
}

1.1 函数声明

一个函数有输入和输出,要在 TypeScript 中对其进行约束,两者都要考虑到,其中函数声明的类型定义较简单:

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

输入多余的(或者少于要求的)参数,是不被允许的:

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

sum(1);
// 应有 2 个参数,但获得 1 个。ts(2554)
// index.ts(1, 25): 未提供 "y" 的自变量。

sum(1, 2, 3); // 应有 2 个参数,但获得 3 个

1.2 函数表达式

最简单的描述函数的方式就是函数类型表达式,应该是怎么样的呢?

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

这段代码不会报错,也是正确的,而还有一种函数表达式的定义是这样的:

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

注意:此箭头,并不是 ES6 中我们熟知的箭头函数哦~

两者的区别是 mySum1 的类型是推导出来的,而 mySum2 是直接定义的,但两者其实一样,不需要深究。

在这里插入图片描述

二、函数签名

在 JavaScript 中,函数除了可以被调用,自己也是可以有属性值的。然而上一节讲到的函数类型表达式并不能支持声明属性,如果我们想描述一个带有属性的函数,我们可以在一个对象类型中写一个调用签名(call signature)。

type DescribableFunction = { 
    
    description: string;
    (someArg: number): boolean; // 参数列表和返回值类型
};
function doSomething(fn: DescribableFunction) { 
    
    console.log(fn.description + " returned " + fn(6));
}

// 测试func
const func = function(someArg: number): boolean { 
    
    return someArg > 5;
};
func.description = "携带的描述";

doSomething(func);

【Typescript入门手册】函数

三、泛型函数

  1. 指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。记住
  2. 所谓泛型就是用一个相同类型来关联两个或者更多的值。

我们经常需要写这种函数,即函数的输出类型依赖函数的输入类型,或者两个输入的类型以某种形式相互关联。让我们考虑这样一个函数,它返回数组的第一个元素:

function firstElement(arr: any[]) { 
     
    return arr[0];
}

注意此时函数返回值的类型是 any,如果能返回第一个元素的具体类型就更好了。

此时我们需要在函数签名里声明一个类型参数 (type parameter):

function firstElement<T>(arr: T[]): T { 
     
    return arr[0];
}

const str = firstElement(['str']);
// const str: string
const bool = firstElement([true]);
// const bool: boolean
const num = firstElement([1]);
// const num: number

通过给函数添加一个类型参数 T,并且在两个地方使用它,我们就在函数的输入(即数组)和函数的输出(即返回值)之间创建了一个关联。现在当我们调用它,一个更具体的类型就会被判断出来;

3.1 推断(Inference)

注意在上面的例子中,我们没有明确指定T的类型,类型是被 TypeScript 自动推断出来的。

我们也可以使用多个类型参数,举个例子:

const res = map([1, 2, 3, 4], (val) => val.split(","));
// (parameter) val: number
// 类型“number”上不存在属性“split”。

注意在这个例子中,TypeScript 既可以推断出 Input 的类型是number ,此时自然不能使用split方法。

3.2 约束(Constraints)

有的时候,我们想关联两个值,但只能操作值的一些固定字段,这种情况,我们可以使用约束(constraint)对类型参数进行限制。

让我们写一个函数,函数返回两个值中更长的那个。为此,我们需要保证传入的值有一个number类型的length属性。我们使用extends语法来约束函数参数:

function longest<Type extends { 
        length: number }>(a: Type, b: Type) { 
     
    if (a.length >= b.length) { 
     
        return a;
    } else { 
     
        return b;
    }
}

const res1 = longest([1, 2, 3], 1);
// ❌ 类型“number”的参数不能赋给类型“{ length: number; }”的参数。
const res2 = longest([1, "2", 3], [1]);
// const res2: (string | number)[]
const res3 = longest('yuguang', '余光');
// const res3: "yuguang" | "余光"

正是因为我们对Type做了{ length: number }限制,我们才可以被允许获取 a b参数的 .length 属性。没有这个类型约束,我们甚至不能获取这些属性,因为这些值也许是其他类型,并没有 length 属性。

基于传入的参数,longerArray和 longerString 中的类型都被推断出来了。

3.3 泛型约束实战(Working with Constrained Values)

这是一个使用泛型约束常出现的错误:

function minimumLength<Type extends { 
        length: number }>(
    obj: Type,
    minimum: number
): Type { 
     
    if (obj.length >= minimum) { 
     
        return obj;
    } else { 
     
        return { 
      length: minimum };
        // 不能将类型“{ length: number; }”分配给类型“Type”。
        // "{ length: number; }" 可赋给 "Type" 类型的约束
        // 但可以使用约束 "{ length: number; }" 的其他子类型实例化 "Type"。
    }
}

这里很绕,我也理解了很久,欢迎大家讨论交流哦~

其中的问题就在于函数理应返回与传入参数相同类型的对象,而不仅仅是符合约束的对象。我们可以写出这样一段反例:

3.4 声明类型参数 (Specifying Type Arguments)

TypeScript 通常能自动推断泛型调用中传入的类型参数,但也并不能总是推断出。举个例子,有这样一个合并两个数组的函数:

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] { 
     
    return arr1.concat(arr2);
}
// 如果你像下面这样调用函数就会出现错误:

const arr1 = combine([1, 2, 3], ["hello"]);
// 不能将类型“string”分配给类型“number”

// 而如果你执意要这样做,你可以手动指定 Type:
const arr2 = combine<string | number>([1, 2, 3], ["hello"]);

【Typescript入门手册】函数

四、写一个好的泛型函数的一些建议

尽管写泛型函数很有意思,但也容易翻车。如果你使用了太多的类型参数,或者使用了一些并不需要的约束,都可能会导致不正确的类型推断。

4.1 类型参数下移(Push Type Parameters Down)

下面这两个函数的写法很相似:

function firstElement1<Type>(arr: Type[]) { 
      
    return arr[0];
}

function firstElement2<Type extends any[]>(arr: Type) { 
      
    return arr[0];
}

// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);

分析:

  • 第一个函数可以推断出返回的类型是 number,但第二个函数推断的返回类型却是 any,因为 TypeScript 不得不用约束的类型来推断 arr[0] 表达式,而不是等到函数调用的时候再去推断这个元素。

4.2 使用更少的类型参数 (Use Fewer Type Parameters)

这是另一对看起来很相似的函数:

// good
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] { 
      
  return arr.filter(func);
}
// bad
function filter2<Type, Func extends (arg: Type) => boolean>(
  arr: Type[],
  func: Func
): Type[] { 
      
  return arr.filter(func);
}

我们创建了一个并没有关联两个值的类型参数 Func,这是一个危险信号,因为它意味着调用者不得不毫无理由的手动指定一个额外的类型参数。Func 什么也没做,却导致函数更难阅读和推断。

4.3 类型参数应该出现两次 (Type Parameters Should Appear Twice)

有的时候我们会忘记一个函数其实并不需要泛型

// bad
function greet<Str extends string>(s: Str) { 
      
    console.log("Hello, " + s);
}
// good
function greet(s: string) { 
      
    console.log("Hello, " + s);
}

记住:类型参数是用来关联多个值之间的类型。如果一个类型参数只在函数签名里出现了一次,那它就没有跟任何东西产生关联。如果一个类型参数仅仅出现在一个地方,强烈建议你重新考虑是否真的需要它。

在这里插入图片描述

五、函数参数

  1. 可选参数
  2. 参数默认值
  3. 剩余参数
  4. 参数解构

5.1 可选参数

如果你已经对函数的参数类型声明,那么输入多余的(或者少于要求的)参数,是不允许的。那么如何定义可选的参数呢?

与接口中的可选属性类似,我们用?表示可选的参数:

function hello(name1: string, name2: string): void { 
      
    console.log(`hello! ${ 
        name1} ${ 
        name2 ? "and" + " " + name2 : ""}`);
}

hello("余光");
// 应有 2 个参数,但获得 1 个
// 未提供 "name2" 的自变量。

function hello1(name1: string, name2?: string): void { 
      
    console.log(`hello! ${ 
        name1} ${ 
        name2 ? "and" + " " + name2 : ""}`);
}

hello1("余光");

需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了:

5.2 参数默认值

在 ES6 中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数:

function hello(name1: string = "余光", name2: string = "yuguang"): void { 
      
    console.log(`hello! ${ 
        name1} ${ 
        name2 ? "and" + " " + name2 : ""}`);
}

hello(); // hello! 余光 and yuguang
hello("小明"); // hello! 小明 and yuguang

是不是又拉回到了我们熟悉的领域?

5.3 剩余参数

ES6 中,可以使用 …rest 的方式获取函数中的剩余参数(rest 参数):

function func(a, ...arg) { 
      
  console.log(a);
  console.log(arg);
}

func(1, 2, 3, 4); // 1, [2, 3, 4]

注意:rest参数只能是最后一个参数

5.4 参数解构

你可以使用参数解构方便的将作为参数提供的对象解构为函数体内一个或者多个局部变量,在 JavaScript 中,是这样的:

type Man = { 
       name: string; age: number };
function sum({ 
       name, age }: Man) { 
      
    console.log(name + age);
}
sum({ 
       a: 10, b: 3, c: 9 });
// 类型“{ a: number; b: number; c: number; }”的参数不能赋给类型“Man”的参数。
// 对象文字可以只指定已知属性,并且“a”不在类型“Man”中。ts

在解构语法后,要写上对象的类型注解。

六、其他类型

这里介绍一些也会经常出现的类型。像其他的类型一样,你也可以在任何地方使用它们,但它们经常与函数搭配使用。

6.1 void

void 表示一个函数并不会返回任何值,当函数并没有任何返回值,或者返回不了明确的值的时候,就应该用这种类型。

function noop() { 
      
    return;
}

在 JavaScript 中,一个函数并不会返回任何值,会隐式返回 undefined,但是 void 和 undefined 在 TypeScript 中并不一样。

6.2 object

这个特殊的类型 object 可以表示任何不是原始类型(primitive)的值 (string、number、bigint、boolean、symbol、null、undefined)。object 不同于空对象类型 { },也不同于全局类型 Object。很有可能你也用不到 Object。

object不同于Object,总是使用object!

6.3 unknown

unknown类型可以表示任何值。有点类似于 any,但是更安全,因为对 unknown 类型的值做任何事情都是不合法的:

function f1(a: any) { 
      
    a.b(); // OK
}
function f2(a: unknown) { 
      
    a.b();
    // 类型“unknown”上不存在属性“b”
}

有的时候用来描述函数类型,还是蛮有用的。你可以描述一个函数可以接受传入任何值,但是在函数体内又不用到 any 类型的值。

你可以描述一个函数返回一个不知道什么类型的值,比如:

function safeParse(s: string): unknown { 
      
    return JSON.parse(s);
}

6.4 never

一些函数从来不返回值:

function fail(msg: string): never { 
      
  throw new Error(msg);
}

never 类型表示一个值不会再被观察到 (observed)。

作为一个返回类型时,它表示这个函数会丢一个异常,或者会结束程序的执行。

当 TypeScript 确定在联合类型中已经没有可能是其中的类型的时候,never类型也会出现:

function fn(x: string | number) { 
      
    if (typeof x === "string") { 
      
        // do something
    } else if (typeof x === "number") { 
      
        // do something else
    } else { 
      
        x; // has type 'never'!
    }
}

在这里插入图片描述

写在最后

本篇文章是《Typescript基础入门》第三篇,到这里就结束了,主要带大家了解一下函数在Ts中的表现,其实到这里有关一门语言中基础部分——类型相关的知识已经聊得差不多了,一起加油~

参考:

  • TypeScript文档
  • TypeScript4中文文档

关于我:

  • 花名:余光
  • 邮箱:webbj97@163.com
  • csdn:传送门

其他沉淀:

  • Github: Js版LeetCode题解
  • 余光的前端成长笔记
  • 高频手撕代码系列

【Typescript入门手册】函数

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/224459.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 上午11:53
下一篇 2026年3月17日 上午11:54


相关推荐

  • ClientToScreen 和ScreenToClient用法

    ClientToScreen 和ScreenToClient用法ClientToScre 是把窗口坐标转换为屏幕坐标 ScreenToClie 是把屏幕坐标转换为窗口坐标屏幕坐标是相对于屏幕左上角的 而窗口坐标是相对于窗口用户区左上角的 VC 下 有些函数使用窗口坐标 有些使用屏幕坐标 使用时要分清 一个窗体分为两部分 系统区和客户区象标题和菜单之类的是系统区 由系统来控制 客户区就是你的地盘喽 Width Heig

    2026年3月17日
    0
  • 手部识别模型

    手部识别模型

    2026年3月15日
    3
  • snappy流式编解码总结

    snappy流式编解码总结介绍 snappy 是谷歌开源的用于数据快速压缩和解压的程序库 它的目标并非实现最大压缩率 而是同时实现非常高的压缩速度和合理的压缩率 snappy 被广泛应用于 google 内部和开源的项目中 例如 Hadoop LevelDB Spark 官方库地址 https github com google snappy 由于最近的工作需要 发现 snappy 还实现了两个变种 这两个变种分别实现了 snappy 算法在本地文件系统和 hadoop 中的流式编解码 以下分别称之为 snappystream 和 hadoop

    2026年3月19日
    2
  • 51单片机12864串口硬件电路图和程序

    51单片机12864串口硬件电路图和程序1 12864BV2 0 液晶显示块 有不同时期生产的 他们在硬件上有可能不一样 串 并口的转换在硬件上的设置有可能不太一样 本实验所用实物如图所示 液晶正面液晶背面 2 程序 文件名 硬件串行 12864Bv2 0 显示 c 描述 该程序实现了 12864 以串行的方式进行通信

    2026年3月26日
    2
  • C多线程编程系列(三)- 线程同步

    C多线程编程系列(三)- 线程同步1 1 简介本章介绍在 C 中实现线程同步的几种方法 因为多个线程同时访问共享数据时 可能会造成共享数据的损坏 从而导致与预期的结果不相符 为了解决这个问题 所以需要用到线程同步 也被俗称为 加锁 但是加锁绝对不对提高性能 最多也就是不增不减 要实现性能不增不减还得靠高质量的同步源语 Synchronizat 但是因为正确永远比速度更重要 所以线程同步在某些场景下是必须的 线程同步有两种源语 Primitive 构造 用户模式 user mode 和内核模式 kern

    2026年3月16日
    2
  • Spring Batch 详解

    Spring Batch 详解JobLauncher 和JobRepository 对应着的Java接口分别是:JobLauncher和JobRepositoryJobLauncher.packageorg.springframework.batch.core.launch;(…)publicinterfaceJobLauncher{publicJobExecutionrun(

    2022年5月8日
    75

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号