TS入门篇 | 详解 TypeScript 函数类型

简介: 在 JavaScript 中,函数是构建应用的一块基石,我们可以使用函数抽离可复用的逻辑、抽象模型、封装过程。在TypeScript中,函数仍然是最基本、最重要的概念之一。下面就来看看TypeScript中的函数类型是如何定义和使用的。

在 JavaScript 中,函数是构建应用的一块基石,我们可以使用函数抽离可复用的逻辑、抽象模型、封装过程。在TypeScript中,函数仍然是最基本、最重要的概念之一。下面就来看看TypeScript中的函数类型是如何定义和使用的。


1. 函数类型定义


(1)直接定义函数类型


函数类型的定义包括对参数返回值的类型定义:

function add(arg1: number, arg2: number): number {
  return x + y;
}
const add = (arg1: number, arg2: number): number => {
  return x + y;
};
复制代码


这里用function字面量箭头函数两种形式定义了add函数。函数参数 arg1 和 arg2 都是数值类型,最后通过相加得到的结果也是数值类型。


如果在这里省略参数的类型,TypeScript 会默认这个参数是 any 类型;如果省略返回值的类型,如果函数无返回值,那么 TypeScript 会默认函数返回值是 void 类型;如果函数有返回值,那么 TypeScript 会根据定义的逻辑推断出返回类型。


需要注意,在TypeScript中,如果函数没有返回值,并且我们显式的定义了这个函数的返回值类型为 undefined,那就会报错:A function whose declared type is neither 'void' nor 'any' must return a value。正确的做法就是上面说的,


将函数的返回值类型声明为void:

function fn(x: number): void {
  console.log(x)
}
复制代码


一个函数的定义包括函数名、参数、逻辑和返回值。为函数定义类型时,完整的定义应该包括参数类型和返回值类型。上面都是在定义函数的指定参数类型和返回值类型。下面来定义一个完整的函数类型,以及用这个函数类型来规定一个函数定义时参数和返回值需要符合的类型。


let add: (x: number, y: number) => number;
add = (arg1: number, arg2: number): number => arg1 + arg2;
add = (arg1: string, arg2: string): string => arg1 + arg2; // error
复制代码


这里定义了一个变量 add,给它指定了函数类型,也就是(x: number, y: number) => number,这个函数类型包含参数和返回值的类型。然后给 add 赋了一个实际的函数,这个函数参数类型和返回类型都和函数类型中定义的一致,所以可以赋值。后面又给它赋了一个新函数,而这个函数的参数类型和返回值类型都是 string 类型,这时就会报如下错误:


不能将类型"(arg1: string, arg2: string) => string"分配给类型"(x: number, y: number) => number"。
参数"arg1"和"x" 的类型不兼容。
不能将类型"number"分配给类型"string"。
复制代码


注意: 函数中如果使用了函数体之外定义的变量,这个变量的类型是不体现在函数类型定义的。


(2)接口定义函数类型

使用接口可以清晰地定义函数类型。下面来使用接口为add函数定义函数类型:

interface Add {
  (x: number, y: number): number;
}
let add: Add = (arg1: string, arg2: string): string => arg1 + arg2; 
// error 不能将类型“(arg1: string, arg2: string) => string”分配给类型“Add”
复制代码


通过接口形式定义了函数类型,这个接口Add定义了这个结构是一个函数,两个参数类型都是number类型,返回值也是number类型。当指定变量add类型为Add时,再要给add赋值,就必须是一个函数,且参数类型和返回值类型都要满足接口Add,显然这个函数并不满足条件,所以报错了。


(3)类型别名定义函数类型

可以使用类型别名来定义函数类型,这种形式更加直观易读:

type Add = (x: number, y: number) => number;
let add: Add = (arg1: string, arg2: string): string => arg1 + arg2; 
// error 不能将类型“(arg1: string, arg2: string) => string”分配给类型“Add”
复制代码


使用type关键字可以给任何定义的类型起一个别名。上面定义了 Add 这个别名后,Add就成为了一个和(x: number, y: number) => number一致的类型定义。上面定义了Add类型,指定add类型为Add,但是给add赋的值并不满足Add类型要求,所以报错了。

注意,这里的=>与 ES6 中箭头函数的=>不同。TypeScript 函数类型中的=>用来表示函数的定义,其左侧是函数的参数类型,右侧是函数的返回值类型;而 ES6 中的=>是函数的实现。


2. 函数参数定义


(1)可选参数

TypeScript 会在编写代码时就检查出调用函数时参数中存在的一些错误:

type Add = (x: number, y: number) => number;
let add: Add = (arg1, arg2) => arg1 + arg2;
add(1, 2);    // success
add(1, 2, 3); // error 应有 2 个参数,但获得 3 个
add(1);       // error 应有 2 个参数,但获得 1 个
复制代码


在JavaScript中,上面代码中后面两个函数调用都不会报错, 只不过add(1, 2, 3)可以返回正确结果3add(1)会返回NaN。而在TypeScript中我们设置了指定的参数,那么在使用该类型时,传入的参数必须与定义的参数类型和数量一致。

但有时候,函数有些参数不是必须的,我们就可以将函数的参数设置为可选参数。可选参数只需在参数名后跟随一个?即可:

type Add = (x: number, y: number, z?: number) => number;
let add: Add = (arg1, arg2, arg3) => arg1 + arg2 + arg3;
add(1, 2);    // success   3
add(1, 2, 3); // success   6
复制代码


上面的代码中,z是一个可选参数,那他的类型就是number | undefined,表示参数 z 就是可缺省的,那是不是意味着可缺省和类型是 undefined 等价呢?来看下面的例子:

function log(x?: number) {
  console.log(x);
}
function log1(x: number | undefined) {
  console.log(x);
}
log();
log(undefined);
log1();    // Expected 1 arguments, but got 0
log1(undefined);
复制代码


可以看到,第三次函数调用报错了,这里的 ?: 表示在调用函数时可以不显式的传入参数。但是,如果声明了参数类型为 number | undefined,就表示函数参数是不可缺省且类型必须是 number 或者 undfined。

需要注意,可选参数必须放在必选参数后面,这和在 JS 中定义函数是一致的。来看例子:

type Add = (x?: number, y: number) => number;  // error 必选参数不能位于可选参数后。
复制代码


在TypeScript中,可选参数必须放到最后,上面把可选参数x放到了必选参数y前面,所以报错了。在 JavaScript 中是没有可选参数这个概念的,只不过在编写逻辑时,可能会判断某个参数是否为undefined,如果是则说明调用该函数的时候没有传这个参数,要做下兼容处理;而如果几个参数中,前面的参数是可不传的,后面的参数是需要传的,就需要在该可不传的参数位置传入一个 undefined 占位才行。


(3)默认参数

在 ES6 标准出来之前,默认参数实现起来比较繁琐:

var count = 0;
function counter(step) {
  step = step || 1;
  count += step;
}
复制代码


上面定义了一个计数器增值函数,这个函数有一个参数 step,即每次增加的步长,如果不传入参数,那么 step 接受到的就是 undefined,undefined 转换为布尔值是 false,所以 step || 1 这里取了 1,从而达到了不传参数默认 step === 1 的效果。

在 ES6 中,定义函数时给参数设默认值直接在参数后面使用等号连接默认值即可:

const count = 0;
const counter = (step = 1) => {
  count += step;
};
复制代码


当为参数指定了默认参数时,TypeScript 会识别默认参数的类型;当调用函数时,如果给这个带默认值的参数传了别的类型的参数则会报错:

const add = (x: number, y = 2) => {
  return x + y;
};
add(1, "ts"); // error 类型"string"的参数不能赋给类型"number"的参数
复制代码


当然也可以显式地给默认参数 y 设置类型:

const add = (x: number, y: number = 2) => {
  return x + y;
};
复制代码


注意:函数的默认参数类型必须是参数类型的子类型,如下代码:

const add = (x: number, y: number | string = 2) => {
  return x + y;
};
复制代码


这里 add 函数参数 y 的类型为可选的联合类型 number | string,但是因为默认参数数字类型是联合类型 number | string 的子类型,所以 TypeScript 也会检查通过。


(3)剩余参数


在 JavaScript 中,如果定义一个函数,这个函数可以输入任意个数的参数,那么就无法在定义参数列表的时候挨个定义。在 ES6 发布之前,需要用 arguments 来获取参数列表。arguments 是一个类数组对象,它包含在函数调用时传入函数的所有实际参数,它还包含一个 length 属性,表示参数个数。下面来模拟实现函数的重载:


function handleData() {
  if (arguments.length === 1) return arguments[0] * 2;
  else if (arguments.length === 2) return arguments[0] * arguments[1];
  else return Array.prototype.slice.apply(arguments).join("_");
}
handleData(2); // 4
handleData(2, 3); // 6
handleData(1, 2, 3, 4, 5); // '1_2_3_4_5'
复制代码


这段代码如果在TypeScript环境中执行,三次handleData的调用都会报错,因为handleData函数定义的时候没有参数。

在 ES6 中,加入了拓展运算符,它可以将一个函数或对象进行拆解。它还支持用在函数的参数列表中,用来处理任意数量的参数:

const handleData = (arg1, ...args) => {
  console.log(args);
};
handleData(1, 2, 3, 4, 5); // [ 2, 3, 4, 5 ]
复制代码


在 TypeScript 中可以为剩余参数指定类型:

const handleData = (arg1: number, ...args: number[]) => {
};
handleData(1, "a"); // error 类型"string"的参数不能赋给类型"number"的参数
复制代码


3. 函数重载


JavaScript 作为一个动态语言是没有函数重载的,只能自己在函数体内通过判断参数的个数、类型来指定不同的处理逻辑:

const handleData = x => {
  if (typeof x === 'string') {
    return Number(x);
  }
  if (typeof x === 'number') {
    return String(x);
  }
  return -1;
};
handleData(996)   // "996"
handleData("996") // 996
handleData(null)  // -1
复制代码


可以看到传入的参数类型不同,返回的值的类型是不同的,我们传入了三个类型的值作为参数,得到的结果都会被推断成 string | number 。


那有没有一种办法可以更精确地描述参数与返回值类型约束关系的函数类型呢? 这就是函数重载。TypeScript 中有的函数重载并不是定义几个同名实体函数,然后根据不同的参数个数或类型来自动调用相应的函数。而是在类型系统层面的,是为了更好地进行类型推断。TypeScript的函数重载通过为一个函数指定多个函数类型定义,从而对函数调用的返回值进行检查


const handleData = (x: string): number;
const handleData = (x: number): string;
const handleData = (x: null): number;
const handleData = (x: string | number | null): any => {
  if (typeof x === 'string') {
    return Number(x);
  }
  if (typeof x === 'number') {
    return String(x);
  }
  return -1;
};
handleData(996)   // "996"
handleData("996") // 996
handleData(false)  // error
复制代码


首先使用function关键字定义了三个同名的函数,这两个函数没有实际的函数体逻辑,而是只定义函数名、参数及参数类型以及函数的返回值类型。第四个使用function定义的同名函数,是一个完整的实体函数,包含函数名、参数及参数类型、返回值类型和函数体。这四个定义组成了一个完整的带有类型定义的函数,前两个function定义的就称为函数重载,而第四个function并不算重载。


在调用 handleData 函数时,TypeScript 会从上到下查找函数重载列表中与入参类型匹配的类型,并优先使用第一个匹配的重载定义。因此,我们需要把最精确的函数重载放到前面。


来看下匹配规则,当调用这个函数并且传入参数的时候,会从上而下在函数重载里匹配和这个参数个数和类型匹配的重载:

  • 第一次调用,传入了一个数字996,从上到下匹配重载是符合第二个的,所以它的返回值应该是字符串类型,返回"996";
  • 第二次调用,传入了一个字符串"996",从上到下匹配重载是符合第一个的,所以它的返回值应该是数字类型。返回996;
  • 第三次调用,传入了一个布尔类型值false,匹配不到重载,所以会报错;


相关文章
|
18天前
|
设计模式 JavaScript 安全
TypeScript性能优化及代码质量提升的重要性、方法与策略,包括合理使用类型注解、减少类型断言、优化模块导入导出、遵循编码规范、加强代码注释等
本文深入探讨了TypeScript性能优化及代码质量提升的重要性、方法与策略,包括合理使用类型注解、减少类型断言、优化模块导入导出、遵循编码规范、加强代码注释等,旨在帮助开发者在保证代码质量的同时,实现高效的性能优化,提升用户体验和项目稳定性。
40 6
|
17天前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
31 2
|
25天前
|
JavaScript 安全 前端开发
TypeScript类型声明:基础与进阶
通过本文的介绍,我们详细探讨了TypeScript的基础与进阶类型声明。从基本数据类型到复杂的泛型和高级类型,TypeScript提供了丰富的工具来确保代码的类型安全和可维护性。掌握这些类型声明能够帮助开发者编写更加健壮和高效的代码,提高开发效率和代码质量。希望本文能为您在使用TypeScript时提供实用的参考和指导。
32 2
|
1月前
|
JavaScript 开发者
在 Babel 插件中使用 TypeScript 类型
【10月更文挑战第23天】可以在 Babel 插件中更有效地使用 TypeScript 类型,提高插件的开发效率和质量,减少潜在的类型错误。同时,也有助于提升代码的可理解性和可维护性,使插件的功能更易于扩展和升级。
|
2月前
|
JavaScript 前端开发
TypeScript【类型别名、泛型】超简洁教程!再也不用看臭又长的TypeScript文档了!
【10月更文挑战第11天】TypeScript【类型别名、泛型】超简洁教程!再也不用看臭又长的TypeScript文档了!
|
2月前
|
JavaScript 前端开发 安全
TypeScript【基础类型】超简洁教程!再也不用看臭又长的TypeScript文档了!
【10月更文挑战第9天】TypeScript【基础类型】超简洁教程!再也不用看臭又长的TypeScript文档了!
|
1月前
|
JavaScript 前端开发 安全
TypeScript进阶:类型系统与高级类型的应用
【10月更文挑战第25天】TypeScript作为JavaScript的超集,其类型系统是其核心特性之一。本文通过代码示例介绍了TypeScript的基本数据类型、联合类型、交叉类型、泛型和条件类型等高级类型的应用。这些特性不仅提高了代码的可读性和可维护性,还帮助开发者构建更健壮的应用程序。
29 0
|
2月前
|
JavaScript 索引
TypeScript(TS)安装指南与基础教程学习全攻略(二)
TypeScript(TS)安装指南与基础教程学习全攻略(二)
56 0
|
2月前
|
JavaScript 前端开发 安全
TypeScript(TS)安装指南与基础教程学习全攻略(一)
TypeScript(TS)安装指南与基础教程学习全攻略(一)
31 0
ts重点学习110-枚举类型的兼容性笔记
ts重点学习110-枚举类型的兼容性笔记
81 0
ts重点学习110-枚举类型的兼容性笔记