TS(三)函数与变量声明

本文目录:

  • 1.函数定义的两种方式
  • 2.函数类型
  • 3.可选参数和默认参数
  • 4.剩余参数
  • 5.函数重载
  • 6.变量声明

函数的几个基本概念:

  • 一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要给函数的参数和返回值都设置一个类型
  • 函数有个特点:函数内部可以使用函数体外部的变量
  • TypeScript 的函数类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。 在 ES6 中,=> 叫做箭头函数
  • 后面我们会用接口来定义函数的形状,避免我们在函数的定义那里写的代码过于臃肿,让代码结构更清晰

1.函数定义的两种方式

// 函数声明
function add(x, y) {
   return x + y;
}
// 函数表达式
let add1 = function add1(x, y) {
     return x + y;
};

上面的代码如果我们进行如下的调用

console.log(add(1.'2'))  //12
console.log(add(2,true))  //3

JS中存在隐式转换,当字符串和数字相加的时候,数字会被转换为字符串,当数字和boolean进行相加的时候,boolean会被转换为数字(false转为0,true转为1),所以上面代码的输出结果并不是我们想要的,而这种bug如果出现在实际项目中,往往是难以定位的。

我们之前说过,任何的js代码都可以不改代码的情况下,直接将后缀名改为ts,可以正常的运行; 是因为ts的类型推断,会自动的根据我们传入的变量和返回的值进行判断

// 我们使用ts的形式来定义两个函数
function add(x: number, y: number): number {
    return x + y;
}
let add2 = function(x: number, y: number): number {
    return x + y;
};

但是我们这里的add2是一个变量,我们没有给它指定类型,它是由等号右边的值 由编译器自动推断出来add2是一个函数的,我们要自定义一个函数类型; 涉及到参数和返回值

2.函数类型
在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。 在 ES6 中,=> 叫做箭头函数,但是在TS中,=>表示函数定义

let add2: (x: number, y: number) => number = function(x: number, y: number): number {
        return x + y;
};

上面就是函数类型, 类似于之前学过的联合类型,枚举类型,类型别名等自定义类型。当然了=>左边的参数名称x,y都是可以随意更改的,在实际工作中,我们不会使用上面的方式去定义函数类型,因为这样写的代码太臃肿了,我们会用接口的形式,这部分知识后面会讲。

Interface Myfn {
    (xll: number,yll: number): number
}
let add3:MyFn = add  //add就是本文上面的代码已经定义好的函数

在ts中,:后面基本都是声明类型

function add(x: number, y: number): number {
  return x + y
}

const add2: (x: number, y: number, z?:number) => number = add

上面这行代码的意思是将add这个函数类型复制给add
同时在上面的代码中,=>并不是箭头函数的意思,而是约束了add2这个函数类型的返回值得返回类型

3.可选参数和默认参数

  • JavaScript里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined
  • TypeScript里参数多了或者少了都是不被允许的;但是我们可以在参数名旁使用 ?实现可选参数的功能
  • 可选参数必须接在必需参数后面,也就是可选参数后面不允许再出现必需参数了
  • 在 ES6 中,我们允许给函数的参数添加默认值,TypeScript会将添加了默认值的参数识别为可选参数
function fullName(firstName: string, lastName?: string) {
  console.log(lastName);
  return firstName + ' ' + lastName;
}

如果不加?, 这个函数我们就只能传递2个参数,多了少了编译都会失败;但是在js里面如果不传,返回值就是undefined
如果加上了?, 那这个函数第一个参数还是必传,第二个参数可传可不传

console.log(fullName('123', '456'));
console.log(fullName('123'));

可选参数必须接在必选参数后面; 下面这样定义函数就会编译报错

function buildName2(firstName?: string, lastName: string) {
    return firstName + ' ' + lastName;
}

接下来我们来看一下默认参数

function buildName2(firstName: string, lastName: string = 'Cat') {
    return firstName + ' ' + lastName;
}
let tomcat1 = buildName2('Tom', 'Cat');
let tom1 = buildName2('Tom');

第二次函数调用只传入了一个参数,因为我们ts会将添加了默认值的参数设置为可选参数, 即使不传递也可以,就是用的默认值Cat

4.剩余参数

  • 必要参数,默认参数和可选参数有个共同点:它们表示某一个参数
  • 想同时操作多个参数,或者你并不知道会有多少参数传递进来, 在js里面使用arguments来访问所有传入的参数
  • 在TypeScript`里,你可以把所有参数收集到一个变量里; 使用...扩展运算符
function getBalls(x: string, ...restOfName: string[]) {
    console.log(restOfName);
    return x + ' ' + restOfName.join(' ');
}
let myBalls = getBalls('basketball', 'football', 'tennis', 'baseball');
console.log(myBalls);

上面的代码中 restOfName实际上是一个数组。所以我们可以用数组的类型来定义它
restOfName剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( ...)后面给定的名字
并且要注意 rest 参数只能是最后一个参数

5.函数重载

  • 同名函数的参数的个数、类型或者顺序必须不同(其中有一个不一样就满足条件了) 叫函数的重载
  • 重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。 为同一个函数提供多个函数类型定义来进行函数重载
  • 在定义重载的时候,一定要把最精确的定义放在最前面。因为查找重载列表,会从第一个重载定义开始使用,如果匹配成功的话就使用,否则就继续向下查下;最后函数实现时,需要使用 |操作符或者?操作符,把所有可能的输入类型全部包含进去(不确实的话可以写any)
  • 函数重载的意义在于能够让你知道传入不同的类型参数得到不同的类型结果,如果传入的参数不同,但是得到的结果(类型)却相同,那么这里就不需要使用函数重载

现在有这样一个需求: 我们有一个add函数,它可以接收string类型的参数进行拼接,也可以接收number类型的参数进行相加
下面是按照之前我们所学知识写的代码

function add(arg1: string | number, arg2: string | number): number | string{
     if (typeof arg1 === 'string' && typeof arg2 === 'string') {
       return arg1 + arg2;
     } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
       return arg1 + arg2;
     }
}
let a1 = add(1, 2);
let a2 = add('a', 'b');

上面的代码在调用函数的时候,定义的变量a1和a2没有去定义类型,只是用了根据等号右边的值去赋值给左边的变量,事实上,a1和a2也不能去指定为string或者number,因为add函数的返回值就是一个联合类型number | string,所以是实际工作中如果发生了这种事情,会导致a1和a2使用变得非常混乱,接下来我们使用函数重载的形式对代码进行优化
先进行函数的重载,定义函数,将参数和返回值的类型定死

function add(arg1: string, arg2: string): string;
function add(arg1: number, arg2: number): number;

接下来进行函数的实现

function add(arg1: string | number, arg2: string | number): number | string{
     if (typeof arg1 === 'string' && typeof arg2 === 'string') {
       return arg1 + arg2;
     } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
       return arg1 + arg2;
     }
}
let a1 = add(1, 2);
let a2 = add('a', 'b');

这时候就可以明确a1和a2的类型了,a1是number类型,a2是string类型
注意,function add(arg1: string | number, arg2: string | number): number | string并不是重载列表的一部分,因此这里只有两个重载:一个是接收数字,另一个接收字符串。 以其它参数调用会产生错误

6.变量声明

  1. 在typescript里面推荐大家使用let关键字来代替大家所熟悉的JavaScript关键字varlet关键字是JavaScript的一个新概念,TypeScript实现了它
  2. 我们先使用typescirpt的语法将var声明变量的几个问题和使用let声明做一个对比

变量声明提升 (作用域规则)

function f(flag: boolean):any {
    if (flag) {
        var x:number = 10;
    }
    return x;
}
console.log(f(true));
console.log(f(false));

大家会发现,我们将变量x定义在if语句块内部,但是却能在外部访问它;这就是var的函数作用域;并且flag为false的时候,也会有打印(undefined),而不是说not defined; 这就是var的变量声明提升
如果换为let, x就只能在if块里面使用,当flag为false时,就会显示x没有定义,通过看编译后的源代码我们也能够发现ts转为js,tsc将变量名直接都改变了,当然就找不到x这个变量的定义了
当用let声明一个变量,它使用的是词法作用域或块作用域;块作用域变量在包含它们的块或for循环之外是不能访问的; 并且没有声明提升

多次重复声明同一个变量不会报错

var a:number = 1;
var a:number = 2;
console.log(a)  

javascript里面不会提升我们是否多次声明了同一个变量,遇到这种情况,js会对忽略后续的声明;但是会执行它的重新赋值操作
如果换位let, 在编译阶段就不会通过;会告诉我们 无法重新声明块范围变量“a”

for循环作用域问题

for (var i:number = 0; i < 5; i++) {
    setTimeout(function f(): void {
        console.log(i);
    }, 1000);
}

最后的结果不是我们希望的 01234,而是5个5,这是因为我们传给setTimeout的每一个函数表达式实际上都引用了相同作用域里(var声明的变量只有函数作用域和全局作用域,)的同一个i; 在循环结束后,i的值为5。 所以当函数被调用的时候,它会打印出5个5

我们可以利用IIFE立即自执行函数表达式,创造多个函数作用域出来,为获取到的变量创建了一个新的变量环境

for (var i:number = 0; i < 5; i++) {
   (function(i) {
        setTimeout(function(): void {
            console.log(i);
        }, 100 * i);
   })(i);
      }

当let声明出现在循环体里时拥有完全不同的行为。 不仅是在循环里引入了一个新的变量环境,而是针对 每次迭代都会创建这样一个新作用域。 这就是我们在使用立即执行的函数表达式时做的事

      for (let i: number = 0; i < 5; i++) {
        setTimeout(function f(): void {
          console.log(i);
        }, 100 * i);
      }

const声明是声明变量的另一种方式

与let声明相似,但是就像它的名字所表达的,它们被赋值后不能再改变。 换句话说,它们拥有与 let相同的作用域规则,但是不能对它们重新赋值

let l1: number = 9;
l1 = 10;
const c2: number = 9;
c2 = 10;

但是对于复杂数据类型有的点特殊

const c2: any = {
    name: 'jack'
};

下面直接改是不行的,因为相当于给变量b重新开辟了一个内存空间,地址发生了改变,constant类型的值不能更改

c2 = {
   name: 'Lucy'
};

但是下面的代码是可以正常执行的,因为改变的是复杂数据类型的值,它们都存储在堆里面,只是一个引用,它们的值就是地址还是保存在栈里面,没有更改

c2.name = 'Lucy';

选择 let还是const

  1. 所有变量除了你计划去修改的都应该使用const。使用 const也可以让我们更容易的推测数据的流动
  2. 基本原则就是如果一个变量不需要对它写入,那么其它使用这些代码的人也不能够写入它们,并且要思考为什么会需要对这些变量重新赋值。

你可能感兴趣的:(TS(三)函数与变量声明)