TypeScript 的基本介绍(三)

  • Post author:
  • Post category:其他


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

❏ 函数的创建和类型

❏ 函数参数情况说明

❏ 泛型函数简单介绍



函数的创建和类型

函数的创建

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

测试变量声明
console.log(getName) //函数
console.log(getNameT) //underfind
console.log(f1) //underfind
//函数声明的创建方式创建(命名函数)
function getName(name:string):string{
 return "getName函数=>姓名:"+name
}

//函数表达式的方式创建(匿名函数)
var getNameT=function(name:string):string{
 return "f1函数=>姓名:" +name
}

//箭头函数
var f1=(name:string):string=>{
 return "f1函数=>姓名:"+name
}
console.log(getName('温丁丁'))
console.log(getNameT('温丁丁'))
console.log(f1('温丁丁'))

因为JavaScript语言中

变量声明提升

的特性,所以通过

函数声明方式创建



命名函数和通过函数表达式方式创建的匿名函数

其使用特征也很不一样.熟悉JavaScript语言中

变量提升

特性的开发者应该非常清楚,所谓的变量提升,指的是真正执行JavaScript代码前解解释器会有个预解析过程,在这个阶段中会检测代码中所有变量声明会把这些声明变量提升当前作用域的顶部

需要注意的是,很多人可能并不能够准确的区分清楚代码中那部分是变量声明,那些部分不属于他 这里进行简单说明

//表示一个变量a,并未赋值
var a;
//表示声明一个变量b,并把123赋值给变量b
//这行代码有两个部分组成,其结构为声明+赋值
//等价于 va b;b=123;两行代码
var b=123;

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


代码说明

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


函数的类型

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

我们可以先看下下面的函数实例代码

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

代码中声明了 add 函数,并指定了这两个函数需要接受两个number类型的参数(分别num1和num2),返回值为字符串类型.其实作为特殊的变量,我们也可以给函数声明类型, 在进行函数声明类型时候,其语法结构和类型声明变量是没有区别的,结构为:

声明变量关键字+变量(函数)名:类型

.

//声明变量
let str:string
//声明变量并做初始化赋值操作
let sum:number=123;

//提供函数变量并提声明函数的类型 (参数和返回值的类型)
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用于表示函数的具体类型,我们可以发现函数的类型由三部分组成:形参+=>标记+返回值类.上面的代码中函数的类型声明和赋值操作是分开处理的,当然你也可以像普通变量的那样把两个操作合二为一,下面给出了该写代码:

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设计函数的时候,如果函数调用时传入的实参和声明时的形参不一致也能工作,但TyepScript中会对函数参数以及参数的个数进行更严格的检查,我们来看下面的代码示例.

//函数声明
function getInfo(name:string,age:number,isSrudent:boolean):string{
 let result:string;
 result='姓名:'+name+'年龄'+age;
 if(isStudent){
  result+="是否为学生?"+isStudent;
 }
 return result;
}
// 函数调用
console.log(getInfo('温丁丁',18,true))//姓名:温丁丁:18是否为学生?true

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

可选参数特性

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

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


注意

所有的可选参数必须位列可选参数列表之后


参数默认值

当函数参数中存在可选参数的时候,在函数体中我们必须要对可选参数是否传递进行检查,这种情况我们使用

可选参数设置默认值

会更合适.

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

//函数声明
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类型的数据.我们可以通过**…形参名:参数类型**的方式来处理这种情况.

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)
//编译通过

function sum1(...numbers:number[]){
 var result:number=0;
 for(var i=0;i<numbers.length;i++){
  result +=number[i]
 }
 return result;
}
sum1(1,2,3);
sum1(2,4,8,10)


建议

:使用…形参的语法来处理函数参数不确定下其编译为JavaScript代码后本质还是遍历arguments,所以在这种情况下考虑让函数接受一个数组参数.



泛型函数简单介绍

泛型说明


泛型编程

:

是一种程序语言的编程风格,它允许开发者使用以后才确定的类型,并在实例化的时候作为参数指定这些类型.


简而言之,当函数中某些参数或返回值的数据类型不确定时,使用泛型编程能够把数据类型类型作为一种参数传递进来.

类型变量T

我们先看一个简单的实例.

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

//说明:该函数接受一个string类型的参数,并返回传入的数据
//缺点:限定了函数的参数类型和返回类型值必须是 string
function f1(str:string){
  return str
}
//wendingdign.com
console.log(f1('wendingding.com'))

//示例代码
function f2(arg:any){
 return arg;
}
console.log(f2('字符串测试'))//字符串
console.log(f2('123'))//123
console.log(f2(true))//true

示例代码003解决了参数可以是任意类型的问题,但简单使用的any类型不足以表达

传入参数和返回值参数必须一致

这个要求,简单说

传入的类型与返回的类型应该是相同的

这个信息点丢失了或者说表现得不够明确.

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

function f3<T>(arg:T):T{
return arg;
}
console.log(f3<string>('字符串'));    //字符串
console.log(f3<number>(345))          //345
console.log(f3<boolean>(false))       //true

console.log(f3("字符串-类型推到"));    //字符串-类型推导
console.log(f3(123))                  //类型推导
console.log(f3(true))

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

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

调用函数的时候,使用< >确定的传入的类型.

利用类型推到来推导确定T的类型

泛型函数使用注意


注意

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

function fn<T>(arg:T):T{
console.log("打印length值="+arg.length)
return arg;
}

//报错:error TS2339;
console.log(fn([1,2,3])); 

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

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

function f1<T>(arg:T[]):T[]{
 console.log("打印length值="+arg.length);
 return arg;
}
console.log(f1([1,2,3]))
console.log(f1(["str","str1","str2"]))

//调整代码组织结构
function f2<T>(arg:Array<T>):Array<T>{
  console.log("数组的长度为="+arg.lenght)
  return arg;
}
console.log(f2[2,4,8,16])   // 打印length值 = 4 [2,4,8,16]


提示

使用泛型函数的时候千万不能先入为主想当然.

本文主要对TypeScript中泛型进行展示介绍.主要包括以下内容

  • 泛型函数
  • 泛型接口(interface)
  • 泛型类(class)
  • 泛型约束

泛型函数类型

在TypeScript这篇中我们介绍什么是泛型函数,它跟普通函数还是有些区别的(泛型函数使用类型变量占位,具体类型值由函数调用传参决定).以前文章中介绍过TyepScript中的数据,以及可选的类型声明.虽然并没有必要(因为可以通过类型推导机制推导出来),但我们确实能够抽取出普通函数的具体类型.下面代码demo函数的函数类型为:

(name:string,age:number)=>string
//函数声明类型
// 声明demo函数
function demo(name:string,age:number):string{
 return "姓名:"+name+"年龄"+age;
}
// 把demo函数赋值给f
let f:{(name:string,age:number):string}=demo
console.log(f("zs",18)) //姓名:zs年龄:18

接下来,我们花点时间研究,泛型函数的函数类型.其实泛型函数的类型与非泛型函数的类型本质上并没有什么不同,只是在最前面增加一个类型变量参数而已.下面给出具体的代码示例.

function demoT<T>(arg:T):T{
  return arg
}
//泛型函数demoT类型为:<T>(arg:T)=>T
let f1:<T>(arg:T)
//使用带有调用标签名的对象字面量来定义泛型函数
let f2:{<T>(arg:T):T}=deomT
//可以使用不同的泛型参数名(这里为X)
let f3:<X>(arg:X)=>X=demoT;
//不使用类型声明
let f4=demoT;

console.log(f1("abc"))   //abc
console.log(f2("哈哈"))  //哈哈
console.log(f3("嘿嘿"))  //嘿嘿
console.log(f4("咕噜"))  //咕噜


提示

:泛型函数的类型声明可以使用不同的泛型参数,只要数量和使用方式一致即可.



泛型接口


接口

(interface) 指在面向对象编程中,不包括数据逻辑但使用函数签名定义行为的抽象类型.

TypeScript 提供了接口特性,TypeScript的接口可以定义数据和行为,也可以扩展其它接口或者类.

在传统面向对象编程范畴中,一个类可以被扩展为另一个类,也可以实现一个或多个接口.实现某个接口可以被看做是签署了一份协议,接口相当于协议,当我们签署协议(实现接口)后,就必须遵守它的规则.

接口本身是抽象类型,其内容(规则)就是属性和方法的签名.

在前文中我们定义了泛型函数demoT,可以把demoT函数的签名抽取并定义接口GenericFn,下面给出实例代码.

//声明泛型函数demoT
function demoT<T>:(arg:T):T{
 return arg;
}
//定义GenericFn接口
interface GerericFn{
 <T>(arg:T):T
}
let fn=GenericFn=demoT;
console.log(fn("哈哈"));  //哈哈

有时候,我们可能需要把泛型参数T抽取成为接口的参数,好处是抽取后我们能够清楚的知道使用的具体泛型是什么,且接口中的其他成员也能够使用,当我们使用泛型接口的时候,传入一个类型参数来指定泛型类型即可,下面给出调整后的代码

//使用泛型doemT
function demoT <T>(arg:T){
 return arg;
}
//定义泛型接口
interface GenericFn<T>{
 (arg:T):T;
}
let f1:GenericFn<number>=demoT;
console.log(f1(123))       // 123

let f2:GenericFn<string>=demoT;
console.log(f2("字符串"))  //字符串



泛型类

//泛型类
class Person<T>{
  //属性部分
  name:T;
  color:T;
  add:(a:T,b:T)=>T
}
//获取实例对象 p1
var p1=new Person<string>();
p1.name="张三";

p1.color="read";
p1.add=function(a,b){
 return a+b;
}
console.log(p1);                   // {name:"张三",color:"Read"}
cosnoe.log(p1.add("ABC","-DEF"))  //ABC-DEF

//获取实例对象p2
var p2=new Person<number>();
p2.name=0;
p2.color=1;
p2.add=function(a,b){
 return a+b;
}
console.log(p2.add(100,200))  //300

上面的代码提供了泛型使用的的简单实例,在定义泛型时,只需要直接把泛型类型放在类名(这里Person)后面即可,通过new 调用类实例化的时候,以<类型>的方式传递,在class中使用泛型可以帮助我们确认类中的很多属性都在使用相同类型,而且能够优化代码结构.



泛型约束

有时候,我们可以需要对泛型进行约束.下面的代码中我们声明了泛型函数fn,并在fn的函数体中执行console.log(“打印length值=”+arg.length); 意在打印参数长度.这份代码在编译的时候会报错,因为无法确定函数调用时出入的参数一定拥有length属性.

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

其实相比于操作any所有类型而言,在这里我们需要对参数类型进行限制,要求传入的参数能够拥有length属性,这种场景可以使用泛型约束

理想中的泛型函数fn的工作情况是:只要传入的参数类型指定的属性length,那么代码就应该正常执行.为此,需要列出对T的约束要求,下面 我们先定义一个接口来描叙特定的约束条件.然后使用这个接口和extends关键字 来实现泛型约束 代码如下

//定义用于描叙约束条件接口
interface hasLenghtP{
 lenght:number
}
//声明fn函数(应用了约束泛型)
function fn<T extends hasLenghtP>(arg:T):T{]
 console.log("打印length值="+arg.length);
 return arg
}
//调用测试
console.log(fn([1,2,3]));              //打印length值 =3 [1,2,3];
console.log(fn({name:'zs',lenght:1}))  //打印length值 =1 对象内容

//说明:字符串会被转换成对象类型(基本包装类型)
cosnole.log(fn('测试')) //打印length值 ==2 测试

// 报错:error
console.log(fn(123)) //错误的提示

上面代码中的fn 泛型函数被定义了约束,因此不再适用于任意类型的参数. 我们需要传入符合约束类型的值,传入的实例必须拥有length属性才能运行


泛型约束中使用多重类型


提示

当声明泛型类型约束中的时候,我们能够关联一种类型类型.但有时候,我们确实需要在泛型约束中使用多重类型,接下来我们研究下她的坑能性和实现方式.

假设现在有一个泛型类型需要被约束,它只允许使用interface_One 和interface_tow两个接口类型,考虑如何实现?

//定义接口:Interface_One和Interface_Two
interface Interface_One{
 func_One();
}

interface Interface_Two{
 func_Two();
}
//泛型类(泛型约束为 Interface_One Interface_Two)
class calssTest<T extends Interface_One,Interface_Two>{
  properDemo:T;
  propertyDemoFunc(){
  this.propertyDemo.func_One();
  this.propertyDemo.func_Two()
 }
}

我们可能会像这样来定义泛型约束,然而上面的代码在编译的时候会抛出错误,也就是说我们不能在定义泛型约束的时候指定多个类型(上面的代码中我们指定了Interface_One和Interface_Two两个类型),如果确实需要设计多重类型约束的泛型,可以通过把多重类型的接口转化为一个超接口来处理,下面给出示代码.

interface Interface_One{
 func_One()
}

interface Interface_Two{
func_Two()
}

interface Interface_T extends Interface_One,Interface_Two{};

//泛型类
class calssTest <T extends Interface_T>
{
 propertyDemo:T;
 propertyDemoFunc(){
  this.propertyDemo.func_One();
  this.PropertyDemo.func_Two();
 }
}

let obj={
 func_One:function(){
   console.log("func_One")
 }
 func_Two:function(){
  console.log("func_Two")
 }
}
//获取实例化对象classTestA
let calssTestA=new classTest();
classTestA.propertyDemo=obj;
classTestA.propertyDemofunc() // func_One  func_Two

//下面错误显示
let calssTestA.properDemo={
 func_Two:function(){
 console.log("func_Two_xxx")
 }
}