本文主要对TypeScript中的函数进行展开介绍。主要包括以下内容

❏ 函数的创建和类型

❏ 函数参数情况说明

❏ 泛型函数简单介绍

一、函数的创建和类型

函数的创建

函数的创建主要有两种方式:通过函数声明创建通过函数表达式创建,在形式上函数又可以被划分文命名函数匿名函数。此外,TypeScript中还提供了箭头函数支持,在声明箭头函数的时候,我们不再使用function关键字转而使用=>标记。

//文件路径 ..07-函数深入讲解/01-函数的创建.ts

//测试变量声明提升
console.log(getName); //函数
console.log(getNameT); //undefined
console.log(f1); //undefined //[001] 函数声明的方式创建(命名函数)
function getName(name:string):string{
return "getName函数=>姓名 :" + name;
} //[002] 函数表达式的方式创建(匿名函数)
var getNameT = function (name:string):string{
return "getNameT函数=>姓名 :" + name;
} //[003] 箭头函数
var f1 = (name:string):string=>{
return "f1函数=>姓名 :" + name;
} console.log(getName("文顶顶")); //getName函数=>姓名 :文顶顶
console.log(getNameT("wendingding.com")); //getNameT函数=>姓名 :wendingding.com
console.log(f1("奥特曼")); //f1函数=>姓名 :奥特曼

因为JavaScript语言中变量声明提升的特性,所以通过函数声明方式创建的命名函数通过函数表达式方式创建的匿名函数其使用特征也很不一样。熟悉JavaScript语言中变量提升特性的开发者应该非常清楚,所谓变量声明的提升,指的是在真正执行JavaScript代码前解释器会有个预解析的阶段,在这个阶段中会检查代码中所有的变量声明并把这些声明提升到当前作用域的顶部。

需要注意的是,很多人可能并不能够准确的区分清楚代码中哪部分是变量声明,哪部分不属于它,这里进行简单说明。
//(1)表示声明一个变量a,并未赋值
var a;
//(2)表示声明一个变量b,并把123赋值给变量b
//这行代码有两部分组成,其结构为 声明 + 赋值
//等价于 var b; b = 123;两行代码
var b = 123;

JavaScript预解析阶段在进行变量声明提升的时候,仅仅会把变量的声明部分进行提升,而赋值操作留在原地。因为函数其实本质上也是变量,所以同样适用上面的规则。

代码说明:观察第一份示例代码,代码中以函数声明方式创建的命名函数getName,其作为函数(变量)声明在预解析阶段会被整体提升,而匿名函数(赋值给了变量getNameT)因为有赋值操作,所以在预解析阶段只会将变量声明这部分(var getNameT)提升到作用域顶部,赋值操作会被留在原地。

函数的类型

我们知道在TypeScript语言中,可以使用可选的类型声明来显示的指定变量的类型,函数其实也算是变量,多以我们可以在声明函数的时候,显示的声明其类型。

我们先看下面的函数示例代码

function add(num1:number,num2:number):string{
return "传入的参数分别为:"+num1+"和 "+ num2 + "add的结果为:" + (num1 + num2);
}

代码中声明了add函数,并指定了该函数需要接受两个number类型的参数(分别为num1和num2),返回值为字符串类型。其实作为特殊的变量,我们也可以给函数也声明类型,在进行函数类型声明的时候,其语法结构同变量可选的类型声明没什么两样,结构均为:声明变量键字 + 变量(函数)名 :类型。

//001 声明变量
let str:string;
//002 声明变量并做初始化赋值操作
let sum:number = 123; //[001]提供函数变量并显示的声明函数的类型(参数和返回值等情况)
let f1:(name:string,age:number) => string;
//赋值操作
f1 = function (name:string,age:number):string
{
return "姓名:" +name + "年龄:" + age;
}
//函数调用
str = f1("zs",18);
console.log(str); //姓名:zs年龄:18

let f1:(name:string,age:number) => string;这行代码中的(name:string,age:number) => string用于表示函数的具体类型,我们可以发现函数的类型声明由三部分组成:形参 + =>标记 + 返回值类型。上面的代码中函数的类型声明和赋值操作是分开处理的,你当然也可以像普通变量那样把这两个操作合二为一,下面给出改写后的代码:

//[002] 声明函数(函数被指定了类型)
let str1:string;
let f2:(name:string,age:number) => string = function (name:string,age:number):string
{
return "姓名:" +name + "年龄:" + age;
}
str1 = f2("zs",18);
console.log(str1); //姓名:zs年龄:18

建议:在使用TypeScript设计函数的时候,不建议像上面示例代码这样来为函数指明类型,因为函数的类型可以从被赋值的函数推断出来,因此我们给函数添加类型声明并不是必需的,相反这样做还会让代码变得冗余且难以理解和阅读。

二、函数参数情况说明

可选参数

在使用JavaScript设计函数的时候,如何函数调用时传入的实参和声明时的形参不一致也能工作,但TypeScript中会对函数参数的类型以及参数的个数进行更严格的检查,我们来看下面的代码示例。


//函数声明
function getInfo(name:string,age:number,isStudent:boolean) : string{ let result:string;
result = "姓名: " + name + "年龄: " + age;
if(isStudent)
{
result += " 是否为学生? " + isStudent;
}
return result;
} //函数调用
console.log(getInfo("文顶顶",18,true)); //姓名: 文顶顶 年龄: 18 是否为学生? true //错误的演示:error TS2554: Expected 3 arguments, but got 2.
console.log(getInfo("文顶顶",20));

如果函数中某些参数并非必须的,我们希望该函数在调用的时候无论是否传递某些参数,函数都要能够继续工作,这需要用上TypeScript为我们提供的可选参数特性。

函数可选参数的具体用法非常简单,我们只需要在函数形参名称后面加上一个?字符即可,调整getInfo方法如下

//文件路径 ../07-函数深入讲解/03-函数的可选参数02.ts

//函数声明
function getInfo(name:string,age:number,isStudent?:boolean) : string{ let result:string;
result = "姓名: " + name + " 年龄: " + age;
if(isStudent)
{
result += " 是否为学生? " + isStudent;
}
return result;
} //函数调用
console.log(getInfo("文顶顶",18,true)); //姓名: 文顶顶 年龄: 18 是否为学生? true
console.log(getInfo("文顶顶",20)); //姓名: 文顶顶 年龄: 20
注意:所有的可选参数必须位于必选参数列表的最后。

参数的默认值

当函数中存在可选参数的时候,在函数体中我们必须要对可选参数是否传递进行检测,这种情况我们使用为可选参数设置默认值会更合适。

在上面的代码中,因为getInfo函数的isStudent属性是可选的,所以我们在函数体内的实现代码中访问isStudent前必须先进行检查,而通过形参名称 : 参数类型 = 默认值的方式能够避免这样做,还能够大大的提升代码的可阅读性。

//文件路径 ../07-函数深入讲解/05-函数的可选参数03.ts

//函数声明
function getInfo(name:string,age:number,isStudent:boolean = false) : string{
return "姓名: " + name + " 年龄: " + age + " 是否为学生? " + isStudent;
} //函数调用
console.log(getInfo("文顶顶",18,true)); //姓名: 文顶顶 年龄: 18 是否为学生? true
console.log(getInfo("文顶顶",20)); //姓名: 文顶顶 年龄: 20 是否为学生? false
注意:所有设置默认值的参数必须位于所有必选参数列表的最后。

参数不确定的函数

我们在设计函数的时候,有时该函数能够接受的参数个数是不确定的,比如说现在需要设计一个计算累加和的函数,该函数能够接受任意多个number类型的数据。我们可以通过...形参名:参数类型的方式来处理这种情况。

//文件路径 ../07-函数深入讲解/06-函数的参数不确定.ts
//[001] 在声明函数的时候,不提供形参和类型声明
//TypeScript编译不通过,当调用时候报错:sum函数接收的参数个数为0
function sum() {
var result;
for (var i = 0; i < arguments.length; i++) {
result += arguments[i];
}
return result;
}
sum(1, 2, 3);
sum(2, 4, 8); //[002] 在声明函数的时候,不提供形参和类型声明
//TypeScript编译不通过,当调用时候报错:sum函数接收的参数个数为0
function sum1(...numbers:number[]) {
var result:number = 0;
for (var i = 0; i < numbers.length; i++) {
result += numbers[i];
}
return result;
}
sum1(1, 2, 3); //7
sum1(2, 4, 8, 10); //24

建议: 使用...形参的语法来处理函数参数不确定情况其编译为JavaScript代码后本质还是遍历arguments,所以其实这种情况,在设计的时候可以考虑让函数接收一个数组参数。

三、泛型函数简单介绍

泛型说明

泛型编程是一种程序语言的编程风格,它允许开发者使用以后才会定义的类型,并在实例化的时候作为参数指定这些类型。简而言之,当函数中某些参数或返回值的数据类型不确定时,使用泛型编程能够把数据类型作为一种参数传递进来。

类型变量T

我们先看一个简单的示例。

假设现在需要设计这样一个函数,它接收一个参数并返回任何传给它的值,参数的类型不指定。在实现这种设计需求的时候,因为函数参数的类型不指定,所以我们可能首先想到的就是使用any类型,给出下面的代码示例。

//文件路径 ../08-泛型函数/01-泛型函数简单介绍.ts

//[001] 示例代码1
//说明 该函数接收一个string类型的参数,并返回传入的数据
//缺点 限定了函数的参数类型以及返回值类型必须是string
function f1(str:string):string{
return str;
} //wendingding.com
console.log(f1("wendingding.com")); //[002] 示例代码2
//说明 该函数接收一个任意类型的参数,并返回传入的数据
function f2(arg:any):any{
return arg;
}
console.log(f2("字符串测试")); //字符串测试
console.log(f2(123)); //123
console.log(f2(true)); //true

示例代码002解决了参数可以是任意类型的问题,但简单使用any类型并不足以表达传入参数和返回值参数必须类型一致这个要求,简单说传入的类型与返回的类型应该是相同的这个信息点丢失了或者说表现得不够明确。

因此,我们需要一种方法来明确的表示返回值的类型与传入参数的类型应该是相同的。下面的代码中,我们使用类型变量来设计一个泛型函数,类型变量是一种特殊的变量,只用于表示类型而不是值。

//[003] 示例代码3
//说明 该泛型函数使用类型变量T来表示接收参数和返回值的类型
function f3<T>(arg:T):T{
return arg;
}
console.log(f3<string>("字符串")); //字符串
console.log(f3<number>(345)); //345
console.log(f3<boolean>(false)); //false console.log(f3("字符串-类型推导")); //字符串-类型推导
console.log(f3(123)); //123
console.log(f3(true)); //true

代码中函数接收参数的类型以及返回值的类型我们使用T这个类型变量来表示,T表示的具体类型由函数调用时具体的实参类型决定。如果实参是字符串,那么T就是string类型,如果实参是布尔类型的值,比如true或者是false,那么T就是boolean类型的。

定义了泛型函数后,有两种调用方式

> ❏  调用函数的时候,使用`< >`明确的传入T的类型。
> ❏ 利用类型推导来确定T的类型。

泛型函数使用注意

使用泛型函数的时候,编译器要求我们在函数体内必须正确的使用这个通用的类型。 换句话说,我们必须把这些参数当做是任意类型的数据来组织代码,否则可能会出现编译错误。

//文件路径 ../08-泛型函数/02-泛型函数使用注意点.ts

//说明 该泛型函数使用类型变量T来表示接收参数和返回值的类型
function fn<T>(arg:T):T{
console.log("打印length值 = " + arg.length);
return arg;
} //报错:error TS2339: Property 'length' does not exist on type 'T'.
console.log(fn([1,2,3]));

上面的代码在编译的时候,编译器报错T类型没有length这个属性。错误的原因在于我们在函数体中使用了arg的.length属性,但是却没有在任何地方指明arg具有这个属性。

类型变量(T)表示的是任意类型,而调用这个函数时传入实参可能是数字或true,它们并不具有.length属性。如果我们能够确定函数参数是数组类型的,而数组元素的类型不确定,那么可以像下面这样来组织代码。

//调整组织代码的方式[001]
function f1<T>(arg:T[]):T[]{
console.log("打印length值 = " + arg.length);
return arg;
}
console.log(f1([1,2,3])); //打印length值 = 3 [1,2,3]
console.log(f1(["str1","str2","demo"])); //打印length值 = 3 ["str1","str2","demo"] //调整组织代码的方式[002]
function f2<T>(arg:Array<T>):Array<T>{
console.log("数组的长度为 = " + arg.length);
return arg;
}
console.log(f2([2,4,8,16])); //打印length值 = 4 [2,4,8,16]
使用泛型函数的时候千万不能先入为主想当然。

备注:该文章所有的示例代码均可以点击在Github托管仓库获取

前端开发系列044-基础篇之TypeScript语言特性(四)的更多相关文章

  1. 从0到1用react+antd+redux搭建一个开箱即用的企业级管理后台系列(基础篇)

    背景 ​ 最近因为要做一个新的管理后台项目,新公司大部分是用vue写的,技术栈这块也是想切到react上面来,所以,这次从0到1重新搭建一个react项目架子,需要考虑的东西的很多,包括目录结构.代码 ...

  2. 前端开发:css基础知识之盒模型以及浮动布局。

    前端开发:css基础知识之盒模型以及浮动布局 前言 楼主的蛮多朋友最近都在学习html5,他们都会问到同一个问题 浮动是什么东西?  为什么这个浮动没有效果?  这个问题楼主已经回答了n遍.今天则是把 ...

  3. ESP8266开发之旅 基础篇① 走进ESP8266的世界

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  4. ESP8266开发之旅 基础篇② 如何安装ESP8266的Arduino开发环境

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  5. ESP8266开发之旅 基础篇③ ESP8266与Arduino的开发说明

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  6. openlayers5-webpack 入门开发系列一初探篇(附源码下载)

    前言 openlayers5-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载 ...

  7. leaflet-webpack 入门开发系列一初探篇(附源码下载)

    前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址 w ...

  8. 【Windows10 IoT开发系列】配置篇

    原文:[Windows10 IoT开发系列]配置篇 Windows10 For IoT是Windows 10家族的一个新星,其针对不同平台拥有不同的版本.而其最重要的一个版本是运行在Raspberry ...

  9. ESP8266开发之旅 基础篇④ ESP8266与EEPROM

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  10. ESP8266开发之旅 基础篇⑥ Ticker——ESP8266定时库

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

随机推荐

  1. 在Winform开发框架支持多种数据库基础上,增加对国产数据库人大金仓的支持

    一个良好的产品,可能往往需要支持多种数据库的接入,根据实际业务的需要进行调整,有时候可能需要2到3种数据库的支持. 在很多应用系统里面,虽然一般采用一种数据库运行,但是由于各种情况的需要,可能业务系统 ...

  2. java 实现发送邮件功能

    最近工作项目中需要使用到邮件功能,参考网上的文章,实现了一个基于java的邮件发送功能:直接上代码: 1.依赖 <dependency> <groupId>org.spring ...

  3. fiddler断点应用

    一.作用 1.模拟网络中断 2.断点时篡改数据 3.测试时做一些极端测试 二.断点步骤 1.全局断点 1)全局断点的两种方式 点击状态栏空白框,点击一下请求前断点,两下请求后断点,三下取消断点 Rul ...

  4. RK356X网口限速

    1. 参考资料 Linux 上的虚拟网络接口,主要要了解一下 IFB,对输入的流量进行整形 https://lyyao09.github.io/2020/06/13/linux/An-introduc ...

  5. python3安装xlutils模块

    下载: https://www.cnpython.com/pypi/xlutils/dl-xlutils-2.0.0-py2.py3-none-any.whl#google_vignette 安装: ...

  6. bootstrap4下拉菜单无法显示问题

    刚才在菜鸟教程学习bootstrap4时在按钮组章节中遇到了下拉菜单,可是自己没有调试出来!!! 我把菜鸟的代码copy(全部)到本地发先可以运行!!! 找了半天原因,可能是自己导入的js文件有错!! ...

  7. redis 配置redis.config

    目录 配置日志位置 配置日志位置 编辑redis.config文件 默认logfile的值为"",修改为指定位置后重启服务. logfile "/usr/local/lo ...

  8. 【安装】Ubuntu 20.04下快速安装ROS2 Foxy

    参考博客 ROS2安装 有的地方原博主打错了,还没改过来,我按我自己的改好了. 有的地方比如github和raw.githubusercontent.com访问不了,我替换成能用的镜像源了,只求一键复 ...

  9. 聊一聊 dotnet 社区对 RISC-V 的支持进展

    我们从Github .NET 社区的相关仓库和Issue 里通过三个方面的简要梳理dotnet 对 RISC-V 的支持: 官方支持截至 2025 年 5月,微软官方的 .NET(dotnet)尚未正 ...

  10. github每次提交代码都要登录

    原因:不要使用https的方式克隆代码,而是用git. 查看源 git remote -v 删除源 git remote rm origin 重新添加项目源(origin后面改成自己的): git r ...