Typescript 函数
前言
虽然 JS/TS 支持面向对象编程,但大部分时候还是在写函数。函数是一等公民。本文介绍下如何在 TypeScript 中使用函数,包括:
- 函数类型声明
- 函数参数类型:可选参数、默认参数、剩余参数
- 函数返回值类型
- this 类型
- 函数重载
函数类型
面试中经常会被问到,JS 中有哪几种数据类型。其中就会有函数类型。
JS 中的函数类型很模糊,准确来说,仅有类型的概念,却无类型的实质。好在有了 TS 强类型的加持,现在可以好好定义一个函数的类型了。
声明一个函数类型,有若干种不同的方式。比如通过 interface 定义函数类型,通过 type 声明函数类型别名,在类声明中声明函数类型等等。
但核心就一点,只关注函数参数的类型和返回值的类型。
下面就从函数的声明入手,来看看函数类型的使用。
函数的声明方式
有三种最简单的函数声明方式。
使用 function
关键字进行声明:
function add(a: number, b: number): number {
return a + b
}
通过函数表达式进行声明:
let add = function (a: number, b: number): number {
return a + b
}
或者使用箭头函数:
let add = (a: number, b: number): number => {
return a + b
}
上面这三种声明函数的方式和 JS 中声明函数别无二致,就是多了两点:
- 函数参数需要给明类型
- 函数返回值也需要给出类型
那么函数类型到底在哪里呢?把鼠标移动到函数名字上就能看到了:
和声明普通变量变量一样:
let name = 'kunwu'
如果没有使用类型注解,那么编译器会自动推导出类型来。上面红框标识出来的类型,就是编译器推导出来的。
函数的类型注解
函数的类型形如 (参数:类型) => 类型
,所以函数的类型注解就是:
let add: (a: number, b: number) => number = function (a, b) {
return a + b
}
通常函数的类型都比较长,此时可以使用 type 类型别名,来声明一个标识符表示该类型:
type Add = (a: number, b: number) => number
let add: Add => number = function (a, b) {
return a + b
}
这样,声明函数类型,使用函数类型,看上去就很简洁直观了。
对象中的函数类型
函数的参数
声明函数时,可以使用类型注解声明参数类型。如果不指定类型,默认类型是 any
。
function add (a: number, b: number) :number {
return a + b
}
可选参数
可选参数使用 ?
标记,需要放到固定参数的后面:
const fn = (a: number, b?:number) => {
if(b) {
return a + b
} else {
return a
}
}
可选参数意味着参数可传可不传。通过编译器的类型推导,可以看到好像可选参数等同于一个联合类型:
但其实并不等同。
可选参数表示参数可传可不传,若传则必须是指定的类型。
而直接将参数类型声明为 string|undefined
,意味着这是个必传参数,必须传实参。
参数默认值
参数的默认值,在参数的类型后面使用 =
声明:
const fn = (a: number, b: number = 10): number => {
return a + b
}
剩余参数
剩余参数是 ES6 中的一个特性,使用剩余运算符 ...
来获取函数实参中未被形参声明的变量,其取得的值是一个数组,比如:
function add(a,b, ...args) {
console.log(args)
}
add(1,2,3,4,5)
则剩余参数 args 就等于 [3, 4, 5]。
TS 中剩余参数也要有类型声明,需要将其声明为数组类型或者元组类型。
function add(a: number, b: number, ...args: number[]) {
console.log(c)
}
剩余参数和其他的固定参数不同。其他的固定参数声明了类型,则必须传该类型的值。而剩余参数虽然也声明了类型,但可传可不传,可传一个,可传多个,比如:
function add(a: number, b: number, ...args: number[]) {
console.log(args)
}
add(1, 2) // [ ]
add(1, 2, 3) // [ 3 ]
add(1, 2, 3, 4) // [ 3, 4 ]
还可以将剩余参数类型声明为元组类型,元组中还可以继续使用可选参数,在参数后使用 ?
表示:
function log( ...args: [string, number, boolean?]) {
console.log(args)
}
log('Shinichi', 17) // [ 'Shinichi', 17 ]
log('Zoro', 19, true) // [ 'Zoro', 19, true ]
函数的返回值类型
函数的返回值类型可以通过类型注解指定。如果不指定的话,TS 编译器能够根据函数体的 return 语句自动推断出返回值类型,因此我们也可以省略返回值类型。
TS 基本类型中有一个 void 类型,表示空类型,它唯一的用处就是用作函数的返回值类型。当一个函数没有 return 语句时,
如果函数使用 return 语句返回了 undefined 值,则返回值类型就为 undefined 而不是 void 了:
但是此时可以将返回值类型使用类型注解指定为 void:
this 类型
JS 函数中到处可见 this 的身影。关于 this 的指向也是前端八股中的基础之基础。
默认情况下 TS 编译器会将函数中的 this 设为 any
类型,也就是说编译器不会对 this 做类型检查,就可以任意使用 this,比如:
function fn() {
this.name = 'Naruto'
this.age = 18
}
同时 TS 编译器又提供了一个编译选项 --noImplicitThis
,开启后会检查 this 的类型,此时需要明确指定其类型,否则会报错,如下:
那么如何为 this 声明类型呢?
声明 this 类型
TS 函数中有一个特殊的 this 参数,它用来指定该函数中用到的 this 的类型,需要定义在形参的第一个位置。
还是上面的例子,要为函数中的 this 指定类型的话,这样写:
function fn(this: {name: string, age: number}) {
this.name = 'Naruto'
this.xxx = 'xxx'
}
直接在函数参数列表中声明 this 类型不太优雅,可以使用 type 关键字声明别名再使用:
type Person = {name: string, age: number}
function fn(this: Person) {
this.name = 'Naruto'
this.age = 18
}
当定义了 this 类型后,函数在调用时也会有所不同,需要使用 call、apply :
type Person = {name: string, age: number}
function fn(this: Person, a: number) {
this.name = 'Naruto'
this.age = 18
}
fn.call({name: 'Naruto', age: 18}, 10)
fn.apply({name: 'Naruto', age: 18}, [10])
像以前那样直接调用函数是错误的:
fn(10) // X
函数重载
面向对象编程有三大特征,封装、继承和多态。多态的表现之一就是函数的重载。
函数重载,就是可以多次声明一个同名函数,但是它们的参数类型不同或者参数个数不同。这样在调用时,可以根据传入参数类型的不同,参数个数的不同,来确定执行的到底是哪一个函数。
函数重载是后端语言的概念,比如 java 中:
// 两个整数的和
public static int add(int a, int b) {
return a+b;
}
// 三个整数的和
public static int add(int a, int b, int c) {
return add(a,b)+c;
}
JS 本身并不支持函数重载。如果多次声明一个函数,则后声明的会覆盖掉先声明的。但是 JS 可以利用 arguments 对象,再加上判断实参的类型,来模拟重载的功能,比如:
function add() {
let args = Array.from(arguments)
if(args.length == 2) {
return args[0] + args[1]
} else if(args.length == 3) {
return args[0] + args[1] + args[3]
}
}
add(1,2)
add(1, 2, 3)
TS 中多次声明一个同名函数,编译器会报错。但是 TS 提供了实现函数重载的方法:先通过 function 关键字声明重载的类型,最后写函数的实现。
function add(a: number, b: number) : number
function add(a: string, b: string) : string
function add(a: number|string, b: number | string) {
if(typeof a === 'number' && typeof b === 'number') {
return a + b
} else if(typeof a === 'string' && typeof b === 'string') {
return a + b
}
}
add(1, 2)
add('10', '20')
由于在声明重载时已经确定了函数的返回值类型,在写函数实现时,就不再需要写返回值类型了。编译器会根据重载类型自动推导。
TS 的函数重载只是一种伪重载,最终还是要靠去判断类型,来执行不同的逻辑。
还有一点要注意,声明函数重载和函数实现必须写在一块,中间不能插入其他语句。
总结
本文介绍了TS 中有关函数的知识,包括函数的声明方式,如何声明函数类型,函数参数和返回值的类型,函数重载以及 this 的类型。大部分内容和 JS 中差不太多,主要是 this 类型和函数重载这两点,需要额外关注下。