11.【TypeScript 教程】函数(Function)

简介: 11.【TypeScript 教程】函数(Function)

TypeScript 函数(Function)

本节介绍 TypeScript 的函数,函数是任何应用程序的基本构建部分,通过函数返回一个计算后的值。


TypeScript 的函数声明中函数类型是极为重要的,函数的参数都需要标注参数类型,这可以帮助编译器进行正确的类型推导。本节还会着重讲解 this 的使用,可以通过编译选项和 this 参数两种方法,正确理解 this 的指向。

1. 解释

在 JavaScript 中,函数是头等(first-class)对象,因为它们可以像任何其他对象一样具有属性和方法。在 JavaScript 中,每个函数都是一个 Function 对象。

TypeScript 又为 JavaScript 函数添加了一些额外的功能,让我们可以更容易地使用:

  • 函数类型
  • 可选参数
  • 默认参数
  • 剩余参数
  • 函数重载

2. 函数类型

在 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()

3. 函数的参数

3.1 参数个数保持一致

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 个参数不符。

3.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 是可选参数,可以省略。

3.3 默认参数

参数可以取默认值,上面介绍的可选参数必须跟在必须参数后面,而带默认值的参数不需要放在必须参数的后面,可随意调整位置

const token = (expired = 60*60, secret: string): void  => {}
// 或
const token1 = (secret: string, expired = 60*60 ): void => {}

代码解释:

第 1 行,带默认值的参数 expired 在参数列表首位。

第 3 行,带默认值的参数 expired 在参数列表末位。

3.4 剩余参数

有的时候,函数的参数个数是不确定的,可能传入未知个数,这时没有关系,有一种方法可以解决这个问题。

通过 rest 参数 (形式为 ...变量名)来获取函数的剩余参数,这样就不需要使用 arguments 对象了。

代码解释:

第 1 行,第二个参数传入剩余参数,且均为字符串类型。

第 7 行,调用函数 assert() 时,除了第一个函数传入一个布尔类型,接下来可以无限传入多个字符串类型的参数。

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

3.5 this 参数

JavaScript 里,this 的值在函数被调用的时候才会被指定,但是这个 this 到底指的是什么还是需要花点时间弄清楚。

默认情况下,tsconfig.json 中,编译选项 compilerOptions 的属性 noImplicitThisfalse,我们在一个对象中使用的 this 时,它的类型是 any 类型。

let triangle = {
  a: 10,
  b: 15,
  c: 20,
  area: function () {
    return () => {
      // this 为 any 类型
      const p = (this.a + this.b + this.c) / 2
      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 的指向,下面介绍两种方法:

第一种,在 tsconfig.json 中,将编译选项 compilerOptions 的属性 noImplicitThis 设置为 true,TypeScript 编译器就会帮你进行正确的类型推断:

let triangle = {
  a: 10,
  b: 15,
  c: 20,
  area: function () {
    return () => {
      const p = (this.a + this.b + this.c) / 2
      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: 10,
  b: 15,
  c: 20,
  area: function (this: Triangle) {
    return () => {
      const p = (this.a + this.b + this.c) / 2
      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,就可以进行正确的类型判断,如果取未定义参数,编译器将直接报错。

4. 函数重载

函数重载是指函数根据传入不同的参数,返回不同类型的数据。

它的意义在于让你清晰的知道传入不同的参数得到不同的结果,如果传入的参数不同,但是得到相同类型的数据,那就不需要使用函数重载。

比如面试中常考的字符反转问题,这里就不考虑负数情况了,只是为了演示函数重载:

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('mybj'))   //jbym
console.log(reverse(23874800))  // 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('mybj'))   // jbym
console.log(reverse(23874800))  // 847832

代码解释:


因为这个反转函数在传入字符串类型的时候返回字符串类型,传入数字类型的时候返回数字类型,所以在前两行进行了两次函数类型定义。在函数执行时,根据传入的参数类型不同,进行不同的计算。


为了让编译器能够选择正确的检查类型,它会从重载列表的第一个开始匹配。因此,在定义重载时,一定要把最精确的定义放在最前面。

5. 使用函数时的注意事项

  1. 如果一个函数没有使用 return 语句,则它默认返回 undefined
  2. 调用函数时,传递给函数的值被称为函数的 实参(值传递),对应位置的函数参数被称为 形参
  3. 在函数执行时, this 关键字并不会指向正在运行的函数本身,而是 指向调用函数的对象
  1. arguments 对象是所有(非箭头)函数中都可用的 局部变量。你可以使用 arguments 对象在函数中引用函数的参数。

6. 小结

本节介绍了 TypeScript 中函数的一些新增功能,编写 TypeScript 代码一定要将类型的概念了解透彻,无论是变量还是函数,都要记得进行类型定义

相关文章
|
19天前
|
存储 C++
【C++】string类的使用③(非成员函数重载Non-member function overloads)
这篇文章探讨了C++中`std::string`的`replace`和`swap`函数以及非成员函数重载。`replace`提供了多种方式替换字符串中的部分内容,包括使用字符串、子串、字符、字符数组和填充字符。`swap`函数用于交换两个`string`对象的内容,成员函数版本效率更高。非成员函数重载包括`operator+`实现字符串连接,关系运算符(如`==`, `<`等)用于比较字符串,以及`swap`非成员函数。此外,还介绍了`getline`函数,用于按指定分隔符从输入流中读取字符串。文章强调了非成员函数在特定情况下的作用,并给出了多个示例代码。
|
1月前
|
JavaScript 前端开发
JavaScript函数是代码复用的关键。使用`function`创建函数
【6月更文挑战第22天】JavaScript函数是代码复用的关键。使用`function`创建函数,如`function sayHello() {...}`或`function addNumbers(num1, num2) {...}`。调用函数如`sayHello()`执行其代码,传递参数按值进行。函数可通过`return`返回值,无返回值默认为`undefined`。理解函数对于模块化编程至关重要。
24 4
|
17天前
|
JavaScript 前端开发 程序员
Typescript 【实用教程】(2024最新版)含类型声明,类型断言,函数,接口,泛型等
Typescript 【实用教程】(2024最新版)含类型声明,类型断言,函数,接口,泛型等
15 0
|
20天前
|
JavaScript
TypeScript(六)函数
TypeScript(六)函数
|
25天前
|
运维 负载均衡 Serverless
函数计算产品使用问题之yaml如果写多个function,可不可以yaml在构建的时候能构建多个函数
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
1月前
|
JSON 数据格式
setInterval函数的function与()=>区别——解决Cannot readproperty‘XXXXXXX‘of undefined异常
setInterval函数的function与()=>区别——解决Cannot readproperty‘XXXXXXX‘of undefined异常
14 0
|
1月前
|
JavaScript 前端开发 API
TypeScript 函数
TypeScript 函数
|
1月前
|
JavaScript 前端开发
typescript 函数类型
typescript 函数类型
|
1月前
|
JavaScript 前端开发
37.【TypeScript 教程】TSLint 与 ESLint
37.【TypeScript 教程】TSLint 与 ESLint
20 0
|
6天前
|
前端开发 JavaScript 安全
TypeScript在React Hooks中的应用:提升React开发的类型安全与可维护性
【7月更文挑战第17天】TypeScript在React Hooks中的应用极大地提升了React应用的类型安全性和可维护性。通过为状态、依赖项和自定义Hooks指定明确的类型,开发者可以编写更加健壮、易于理解和维护的代码。随着React和TypeScript的不断发展,结合两者的优势将成为构建现代Web应用的标准做法。