TypeScript 函数(Function)
本节介绍 TypeScript 的,是任何应用程序的基本构建部分,通过返回计算后的值。
TypeScript 的声明中类型是极为重要的,的参数都需要标注参数类型,这可以帮助编译器进行正确的类型推导。本节还会着重讲解 this
的使用,可以通过编译选项和 this 参数两种,正确理解 this 的指向。
在 JavaScript 中,是头等(first-class)对象,因为它们可以像任何其他对象一样具有和。在 JavaScript 中,每个都是 Function
对象。
TypeScript 又为 JavaScript 了一些额外的,让我们可以更容易地使用:
在 TypeScript 中编写,需要给形参和返回值指定类型:
const add = function(x: number, y: number): string {
return (x + y).toString()
}
解释:
参数 x 和 y 都是 number 类型,两个参数相加后将其类型转换为 string, 所以整个的返回值为 string 类型。
上面的只是对 =
等号右侧的匿名进行了类型定义,等号左侧的 add
同样可以类型:
const add: (x: number, y: number) => string = function(x: number, y: number): string {
return (x + y).toString()
}
可以看到,等号左侧的类型定义由两部分组成:参数类型和返回值类型,通过 =>
符号来连接。
这里要注意:类型的 =>
和 箭头的 =>
是不同的含义。
通过箭头改写一下刚才写的:
const add = (x: number, y: number): string => (x + y).toString()
等号左右两侧书写完整:
// 只要参数位置及类型不变,变量可以自己定义,比如把两个参数定位为 a b
const add: (a: number, b: number) => string = (x: number, y: number): string => (x + y).toString()
TypeScript 中每个参数都是必须的。 这不是指不能传递 null 或 undefined 作为参数,而是说编译器会检查是否为每个参数都传入了值。简短地说,传递给的参数个数必须与期望的参数个数一致。
const fullName = (firstName: string, lastName: string): string => `${firstName}${lastName}`
let result1 = fullName('Sherlock', 'Holmes')
let result2 = fullName('Sherlock', 'Holmes', 'character') // Error, Expected 2 arguments, but got 3
let result3 = fullName('Sherlock') // Error, Expected 2 arguments, but got 1
解释:
第 1 行,需要传入 2 个字符串类型参数的类型定义。
第 4 行,result2
传入了 3 个参数,与声明的 2 个参数不符。
第 5 行,result3
只传入了 1 个参数,同样与声明的 2 个参数不符。
在 JavaScript 中每个参数都是可选的,可传可不传。没传参的时候,它的值就是 undefined。 而在 TypeScript 里我们可以在参数名旁使用 ?
实现可选参数的,可选参数必须跟在必须参数后面。
const fullName = (firstName: string, lastName?: string): string => `${firstName}${lastName}`
let result1 = fullName('Sherlock', 'Holmes')
let result2 = fullName('Sherlock', 'Holmes', 'character') // Error, Expected 1-2 arguments, but got 3
let result3 = fullName('Sherlock') // OK
解释:
第 1 行,firstName 是必须参数,lastName 是可选参数。
第 4 行,传入了 3 个参数,与声明的 2 个参数不符。
第 5 行,lastName 是可选参数,可以省略。
参数可以取认值,上面介绍的可选参数必须跟在必须参数后面,而带认值的参数不需要放在必须参数的后面,可随意调整位置:
const token = (expired = *, secret: string): void => {}
// 或
const token1 = (secret: string, expired = * ): void => {}
解释:
第 1 行,带认值的参数 expired 在参数列表首位。
第 3 行,带认值的参数 expired 在参数列表末位。
有的时候,的参数个数是不确定的,可能传入未知个数,这时没有关系,有一种可以这个问题。
通过 rest 参数
(形式为 ...变量名
)来的剩余参数,这样就不需要使用 arguments
对象了。
function assert(ok: boolean, ...args: string[]): void {
if (!ok) {
throw new Error(args.join(' '));
}
}
assert(false, '过大', '只能jpg格式')
解释:
第 1 行,第二个参数传入剩余参数,且均为字符串类型。
第 7 行, assert()
时,除了第传入布尔类型,接下来可以无限传入多个字符串类型的参数。
TIP:注意 rest 参数
只能是最后参数。
JavaScript 里,this 的值在被的时候才会被指定,但是这个 this 到底指的是什么还是需要花点时间弄清楚。
认情况下,tscon.json
中,编译选项 compilerOptions
的 noImplicitThis
为 false
,我们在对象中使用的 this 时,它的类型是 any 类型。
let triangle = {
a: ,
b: ,
c: ,
area: function () {
return () => {
// this 为 any 类型
const p = (this.a + this.b + this.c) /
return Math.sqrt(p * (p - this.a) * (p - this.b) *(p - this.c))
}
}
}
const myArea = triangle.area()
console.log(myArea())
解释:
在实际工作中 any 类型是非常危险的,我们可以任意到 any 类型的参数上,比如将 const p = (this.a + this.b + this.c) / 2
这句改为 const p = (this.d + this.d + this.d) / 2
也不会报错,这很容易造成不必要的问题。
所以我们应该明确 this 的指向,下面介绍两种:
第一种,在 tscon.json
中,将编译选项 compilerOptions
的 noImplicitThis
设置为 true
,TypeScript 编译器就会帮你进行正确的类型推断:
let triangle = {
a: ,
b: ,
c: ,
area: function () {
return () => {
const p = (this.a + this.b + this.c) /
return Math.sqrt(p * (p - this.a) * (p - this.b) *(p - this.c))
}
}
}
const myArea = triangle.area()
console.log(myArea())
解释:
将 noImplicitThis
设置为 true
以后,把鼠标放在第 7 行的 this
上,可以看到:
this: {
a: number;
b: number;
c: number;
area: () => () => number;
}
这时,TypeScript 编译器就能准确的知道了 this 的类型,如果取不存在于 this 中的 d
,将会报错 Property 'd' does not exist on type '{ a: number; b: number; c: number; area: () => () => any; }'
除了这种,我们还可以通过 this 参数
这种形式来 this 为 any 类型这一问题。提供显式的 this
参数,它出现在参数列表的最前面:
// 语法
function f(this: void) {
}
改造刚才的例子:
interface Triangle {
a: number;
b: number;
c: number;
area(this: Triangle): () => number;
}
let triangle: Triangle = {
a: ,
b: ,
c: ,
area: function (this: Triangle) {
return () => {
const p = (this.a + this.b + this.c) /
return Math.sqrt(p * (p - this.a) * (p - this.b) *(p - this.c))
}
}
}
const myArea = triangle.area()
console.log(myArea())
解释:
我们声明了接口 Triangle
,其中的类型显式的传入了 this
参数,这个参数的类型为 Triangle
类型(第 5 行):
area(this: Triangle): () => number;
此时,在第 14 行,this
指向 Triangle
,就可以进行正确的类型判断,如果取未,编译器将直接报错。
重载是指根据传入不同的参数,返回不同类型的数据。
它的意义在于让你清晰的知道传入不同的参数得到不同的结果,如果传入的参数不同,但是得到相同类型的数据,那就不需要使用重载。
比如面试中常考的字符反转问题,这里就不考虑负数情况了,只是为了演示重载:
function reverse(target: string | number) {
if (typeof target === 'string') {
return target.split('').reverse().join('')
}
if (typeof target === 'number') {
return +[...target.toString()].reverse().join('')
}
}
console.log(reverse('imooc')) // coomi
console.log(reverse()) // 847832
编译器并不知道入参是什么类型的,返回值类型也不能确定。这时可以为同提供多个类型定义来进行重载。
(通过 --downlevelIteration
编译选项对器和迭代器协议的)
function reverse(x: string): string
function reverse(x: number): number
function reverse(target: string | number) {
if (typeof target === 'string') {
return target.split('').reverse().join('')
}
if (typeof target === 'number') {
return +[...target.toString()].reverse().join('')
}
}
console.log(reverse('imooc')) // coomi
console.log(reverse()) // 847832
解释:
因为这个反转在传入字符串类型的时候返回字符串类型,传入数字类型的时候返回数字类型,所以在前两行进行了两次类型定义。在执行时,根据传入的参数类型不同,进行不同的计算。
为了让编译器能够选择正确的检查类型,它会从重载列表的第开始匹配。因此,在定义重载时,一定要把最精确的定义放在最前面。
本节介绍了 TypeScript 中的一些新增,编写 TypeScript 一定要将类型的概念了解透彻,无论是变量还是,都要记得进行类型定义。