TypeScript 定义函数的四种方式
第一种方式可以直接调用,后三种需要先实现定义的函数再调用。
第一种 函数声明式:
function sum(x: number, y: number): number {
return x + y
}
// 调用时形参和实参一一对应
sum(1, 2)
第二种 函数表达式:
let sum: (x: number, y: number) => number = (a, b) => a + b
// 或
let sumX = (a: number, b: number): number => a + b
sum(2, 2)
sumX(1, 4)
第三种 接口实现:
interface ISum {
(x: number, y: number): number
}
// 跟变量声明是等价的:let ISum: (a: number, b: number) => number
let sum: ISum = (a, b) => a + b
sum(4, 2)
第四种 类型别名:(推荐方式)
type ISum = (x: number, y: number) => number
// 应用如下:
let sum: ISum = (a, b) => a + b
sum(3, 2)
函数类型
函数类型包含两部分:参数类型和返回值类型。
- 参数名不一定要相同,只要参数类型匹配,那么就认为它是有效的函数类型。
const sum: (num1: number, num2: number) => number = (x, y) => x + y
- 返回值类型是函数类型的必要部分,如果函数没有返回任何值,则返回值类型为 void。
const sum: (num1: number, num2: number) => void = (x, y) => {
console.log(x + y)
}
类型推断
如果在赋值语句的一边指定了类型但另一边没有类型的话,TypeScript 编译器会自动识别出类型, 这叫做“按上下文归类”,是类型推论的一种。
const getSum: (x: number, y: number) => number = (x, y) => x + y
可选参数
JavaScript 里,每个参数都是可选的。没传参的时候,值就是 undefined。但在 TypeScript 中函数参数默认都是必传的,必传的意思并不是不能传递 null 和 undefined 作为实参,而是编译器会检查是否为每个参数传入了值。简而言之,编译器会检查传入实参的个数是否和形参相同。
function bar(name: string, age: number): string {
return `${name} age is ${age}`
}
bar('jack', 12) // ok
bar('nike') // Expected 2 arguments, but got 1.
bar('rose', 12, 'shanghai') // Expected 2 arguments, but got 3.
TypeScript 的可选参数需要在参数名后使用 ?
标识符 实现可选参数的功能。 比如上例希望 age 是可选的:
function bar(name: string, age?: number): string {
if (age) return `${name} age is ${age}`
return `the name is ${name}`
}
bar('jack', 12) // ok
bar('nike') // ok
bar('rose', 12, 'shanghai') // Expected 1-2 arguments, but got 3.
注意: 可选参数必须在必选参数后面,且后面不能再有必选参数
参数默认值
可以通过为参数提供一个默认值,当参数是可选的且没有传值或传递的值是 undefined 时,则会使用参数的默认值。
function fullName(firstName: string, lastName: string = 'Smith') {
return `${firstName} ${lastName}`
}
fullName('Bob') // Bob Smith
fullName('Bob', undefined) // Bob Smith
fullName('Bob', 'Adams', 'Sr.') // Expected 1-2 arguments, but got 3.
fullName('Bob', 'Adams') // Bob Adams
参数默认值与可选参数不同之处:
- 没有传值时默认参数是取默认值,而可选参数的值是 undefined
- 带默认值的参数不需要放在必选参数的后面。如果带默认值的参数出现在必选参数前面,则调用时必须明确的传入
undefined
值来取得默认值
剩余参数
TypeScript 的剩余参数和 ES6 的剩余参数一样。
interface ITotal {
(pre: number, cur: number): number
}
function sum(num1: number, ...rest: number[]): number {
const handle: ITotal = (pre, cur) => pre + cur
return rest.reduce(handle, num1)
}
sum(1, 2, 3, 4, 5, 6, 7) // 28
this
this 与箭头函数
TypeScript 在 noImplicitThis
模式下,不允许 this 上下文隐式定义。
const person = {
name: 'Mike',
getName() {
return function () {
console.log(this.name)
}
}
}
const getName = person.getName()
getName()
上例函数中的 this 在 noImplicitThis
模式开启时报错(this' implicitly has type 'any' because it does not have a type annotation),未开启时指向 window。
可以将返回函数设置成箭头函数解决该问题。
const person = {
name: 'Mike',
getName() {
return () => {
console.log(this.name)
}
}
}
const getName = person.getName()
getName() // 'MIke'
但上面的代码还是会存在一些问题。因为即使能够保证箭头函数里面的 this 与外层函数的 this 保持一致, 但是外层函数的 this 不一定就是 person 这个对象。函数中的 this 依旧是 any 类型。
this 参数
在 JavaScript 中,this 不能用做变量或参数名,所以 TypeScript 使用语法空间来让你在函数体中声明 this 的类型。this 类型声明必须放在参数的首位:
interface IPerson {
name: string
getInfo(this: IPerson, age: number): string
}
const info: IPerson = {
name: 'jack',
getInfo(age) {
return this.name + age
}
}
info.getInfo(23) // ✅
const info1: IPerson = {
name: 'rose',
getInfo(age) {
return this.name + age
}
}
info1.getInfo(34) // ✅
const obj = {
name: 'mike'
}
info.getInfo.call(obj, 45) // ❌
// obj 不是IPerson的类型
重载
函数重载允许一个函数通过不同数量或类型的参数,返回不同类型的值。
比如:实现一个函数 reverse,输入数字的时候,输出反转的数字,输入字符串的时候,输出反转的字符串。
通过联合类型实现:
function reverse(val: number | string): number | string {
if (typeof val === 'number') {
return Number(val.toString().split('').reverse().join(''))
} else if (typeof val === 'string') {
return val.split('').reverse().join('')
}
}
联合类型的缺陷是不能精确表达不同的输入类型所对应的输出类型。 可以通过函数重载定义多个函数类型。
函数重载实现:
function reverse(num: number): number
function reverse(str: string): string
function reverse(val: any): number | string | boolean {
if (typeof val === 'number') {
return Number(val.toString().split('').reverse().join(''))
} else if (typeof val === 'string') {
return val.split('').reverse().join('')
}
return false
}
console.log(reverse(123456)) // 654321
console.log(reverse('sina')) // 'anis'
以上前两个函数是函数重载列表,第三个是函数实体。
注意:重载只能通过 function
声明。
类型谓词(is)
在 TypeScript 中,函数还支持另外一种特殊的类型描述,如下示例 :
function isString(s): s is string { // 类型谓词
return typeof s === 'string';
}
function isNumber(n: number) {
return typeof n === 'number';
}
function operator(x: unknown) {
if(isString(x)) { // ok x 类型缩小为 string
}
if (isNumber(x)) { // ts(2345) unknown 不能赋值给 number
}
}
在上述代码中,在添加返回值类型的地方,通过“参数名 + is + 类型”
的格式明确表明了参数的类型,进而引起类型缩小,所以类型谓词函数的一个重要的应用场景是实现自定义类型守卫。
类型谓词只能用来定义自定义类型守卫,实际上是告诉引擎,当守卫条件成立的情况下(返回 true),将被守卫的类型缩小到 is 指定的更明确的类型。