TypeScript(三)类型缩小、函数类型、this、函数重载

本文整理来自深入Vue3+TypeScript技术栈-coderwhy大神新课,只作为个人笔记记录使用,请大家多支持王红元老师。

类型缩小

什么是类型缩小呢?
类型缩小的英文是 Type Narrowing,我们可以通过类似于 typeof padding === "number" 的判断语句,来改变TypeScript的执行路径,在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为缩小,而我们编写的 typeof padding === "number 可以称之为 类型保护(type guards)。

常见的类型保护有如下几种:

  • typeof
  • 平等缩小(比如===、!==)
  • instanceof
  • in
  • 等等...

typeof

在 TypeScript 中,检查返回的值typeof是一种类型保护,因为我们可以根据不同的值进行不同的编码。

平等缩小

我们可以使用switch或者相等的一些运算符来表达相等性(比如===, !==, ==, != )。

instanceof

JavaScript 有一个运算符来检查一个值是否是另一个值的“实例”。

class Student {
  studying() {}
}

class Teacher {
  teaching() {}
}

function work(p: Student | Teacher) {
  if (p instanceof Student) {
    p.studying()
  } else {
    p.teaching()
  }
}

const stu = new Student()
work(stu)

in

Javascript 有一个in运算符,用于确定对象是否具有带名称的属性,如果指定的属性在指定的对象或其原型链中,则in运算符返回true。

//定义对象类型别名
type Fish = {
  swimming: () => void
}

type Dog = {
  running: () => void
}

function walk(animal: Fish | Dog) {
  if ('swimming' in animal) {
    animal.swimming()
  } else {
    animal.running()
  }
}

const fish: Fish = {
  swimming() {
    console.log("swimming")
  }
}

walk(fish)

TypeScript函数类型

在JavaScript开发中,函数是重要的组成部分,并且函数可以作为一等公民(可以作为参数,也可以作为返回值进行传递)。那么在使用函数的过程中,函数是否也可以有自己的类型呢?我们可以编写函数类型的表达式(Function Type Expressions)来表示函数类型。

// 定义函数
function foo() {}

// 定义函数类型
type FooFnType = () => void
// 函数作为参数
function bar(fn: FooFnType) {
  fn()
}
// 调用函数
bar(foo)
// 定义函数类型
type AddFnType = (num1: number, num2: number) => number
// 明确写出类型注解
const add: AddFnType = (a1: number, a2: number) => {
  return a1 + a2
}
// calc函数有三个参数,前两个参数是number,第三个参数是一个函数,再将前两个参数传入到第三个函数参数中,然后将函数的结果返回
function calc(n1: number, n2: number, fn: (num1: number, num2: number) => number) {
  return fn(n1, n2)
}

//调用
const result1 = calc(20, 30, function(a1, a2) {
  return a1 + a2
})
console.log(result1)

//调用
const result2 = calc(20, 30, function(a1, a2) {
  return a1 * a2
})
console.log(result2)

在上面的语法中 (num1: number, num2: number) => number,代表的就是一个函数类型。这个函数接收两个参数:num1和num2,并且都是number类型,并且返回一个number。在某些语言中,可能参数名称num1和num2可以省略,但是TypeScript是不可以的:

参数的可选类型

我们可以指定某个参数是可选的:

这个时候参数x依然是有类型的,其实就是联合类型:number | undefined,如下:

另外可选类型要在必传参数的后面,否则会报错:

默认参数

从ES6开始,JavaScript是支持默认参数的,TypeScript也是支持默认参数的:

这时候y的类型其实就是 number | undefined 联合类型。

有默认值的参数一般写在后面,以防有歧义,一般参数的顺序是:必传参数 - 有默认值的参数 - 可选参数。

剩余参数

从ES6开始,JavaScript也支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中。

function sum(initalNum: number, ...nums: number[]) {
  let total = initalNum
  for (const num of nums) {
    total += num
  }
  return total
}

console.log(sum(20, 30))
console.log(sum(20, 30, 40))
console.log(sum(20, 30, 40, 50))

可推导的this类型

this是JavaScript中一个比较难以理解和把握的知识点,coderwhy在公众号也有一篇文章专门讲解this:https://mp.weixin.qq.com/s/hYm0JgBI25grNG_2sCRlTA

因为this在不同的情况下会绑定不同的值,所以对于它的类型就更难把握了,那么,TypeScript是如何处理this呢?我们先来看一个例子:

上面的代码是可以正常运行的,也就是TypeScript在编译时,认为我们的this是可以正确去使用的,TypeScript认为函数 sayHello 有一个对应的this的外部对象 info,所以在使用时,就会把this当做该info对象。

不确定的this类型

但是对于某些情况来说,我们并不知道this到底是什么?

这段代码运行会报错。这里我们再次强调一下,TypeScript进行类型检测的目的是让我们的代码更加的安全,所以这里对于 sayHello 的调用来说,我们虽然将其放到了info中,通过info去调用,this依然是指向info对象的,但是对于TypeScript编译器来说,这个代码是非常不安全的,因为我们也有可能直接调用函数,或者通过别的对象来调用函数。

指定this的类型

这个时候,通常TypeScript会要求我们明确的指定this的类型:

type ThisType = { name: string };

// 指定this的类型
function eating(this: ThisType, message: string) {
  console.log(this.name + " eating", message);
}

const info = {
  name: "why",
  eating: eating,
};

// 隐式绑定
info.eating("哈哈哈");

// 显示绑定
eating.call({name: "kobe"}, "呵呵呵")
eating.apply({name: "james"}, ["嘿嘿嘿"])

函数的重载

在TypeScript中,如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加,应该如何编写呢?我们可能会这样来编写,但是其实是错误的:

如果我们通过联合类型:

function add(a1: number | string, a2: number | string) {
  if (typeof a1 === "number" && typeof a2 === "number") {
    return a1 + a2
  } else if (typeof a1 === "string" && typeof a2 === "string") {
    return a1 + a2
  }
  // return a1 + a2;
}

add(10, 20)

通过联合类型有两个缺点:

  1. 进行很多的逻辑判断(类型缩小)
  2. 返回值的类型依然是不能确定

那么这个代码应该如何去编写呢?
在TypeScript中,我们可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用,一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现。

Swift中的函数重载:函数名相同,参数个数不同 或 参数类型不同 或 参数标签不同。
TypeScript中的函数重载:函数名相同,但是参数不同的几个函数,就是函数的重载。

sum函数的重载

比如我们对sum函数进行重构,在我们调用sum的时候,它会根据我们传入的参数类型来决定执行函数体时,到底执行哪一个函数的重载签名。

// 函数的重载: 函数的名称相同, 但是参数不同的几个函数, 就是函数的重载
function add(num1: number, num2: number): number; // 没函数体
function add(num1: string, num2: string): string; // 没函数体

// 有实现体的函数,不能直接被调用
function add(num1: any, num2: any): any {
  if (typeof num1 === 'string' && typeof num2 === 'string') {
    return num1.length + num2.length
  }
  return num1 + num2
}

const result = add(20, 30) // 匹配第一个函数,最后会把第三个函数的实现当做函数体来执行
const result2 = add("abc", "cba") // 匹配第二个函数,最后会把第三个函数的实现当做函数体来执行
console.log(result) // 50
console.log(result2) //6

但是注意,有函数体的函数,是不能直接被调用的,所以上面第三个函数不能直接被调用,否则报错:

// 在函数的重载中, 实现函数是不能直接被调用的
add({name: "why"}, {age: 18})

TypeScript的函数重载,不能有函数体,并且有函数体的函数不能直接被调用,只能先匹配没函数体的函数,最后再调用有函数体的函数的实现,TypeScript的函数重载和其他语言的函数重载是有点不一样。

函数重载练习

我们现在有一个需求:定义一个函数,可以传入字符串或者数组,获取它们的长度。这里有两种实现方案。
方案一:使用联合类型来实现;
方案二:实现函数重载来实现;

这里肯定是使用联合类型来实现更方便,我们优先选择使用联合类型。

你可能感兴趣的:(TypeScript(三)类型缩小、函数类型、this、函数重载)