前端开发系列044-基础篇之TypeScript语言特性(四)

本文主要对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托管仓库获取

posted on 2022-12-12 10:02  文顶顶  阅读(31)  评论(0编辑  收藏  举报

导航