Skip to content

🔥更新:2024-12-09📝字数: 0 字⏱时长: 0 分钟

第一章:联合类型和交叉类型

1.1 扫清概念

  • 交集是指两个集合中都包含的元素组成的集合。例如:集合 A = {1, 2, 3, 4, 5}集合 B = {3, 4, 5, 6, 7}交集C = {3, 4, 5}

交集_百度百科

注意⚠️:在编程中,通常使用逻辑与&)来表示交集的关系。

  • 并集是指两个集合中包含的所有元素组成的集合。例如:集合 A = {1, 2, 3, 4, 5} 和集合 B = {3, 4, 5, 6, 7}并集D = {1, 2, 3, 4, 5, 6, 7}

image-20240126135802067

注意⚠️:在编程中,通常使用逻辑或|)来表示并集的关系。

1.2 联合类型

1.2.1 概述

  • TS 的类型系统允许我们使用各种运算符现有类型构建新类型
  • 联合类型(Union Tpye)是由两个多个其他类型组成的类型,表示可以是其中任何一种类型的值。我们将这些类型中的每一种都称为联合成员(union’s members)。

1.2.2 定义联合类型

  • 定义联合类型很简单,只需要使用 | 来组合类型即可。

  • 示例:

ts
let x: string | number; // 定义联合类型

x = "123"

x = "abc"

export { }
  • 示例:
ts
function print(x: string | number) { // 定义联合类型
  
}

print(123);
print("abc")

export { }

1.2.3 使用联合类型

  • 提供和联合类型匹配的值很简单,只需要保证是符合联合类型中任意类型的值即可
  • 但是,如果有一个联合类型的值,我们该怎么去使用它?通常,我们会使用类型缩小来让 TS 根据代码结构,推断出值更加具体的类型。

注意⚠️:在 TS 中类型缩小有很多种,后面将会一一讲解。

  • 示例:
ts
function print(x: string | number) { // 定义联合类型
  if (typeof x === "string") { // 类型缩小
    console.log("x", x.toUpperCase())
  } else if (typeof x === "number") { // 类型缩小
    console.log("x", x * 2)
  }
}

print(123);
print("abc")

export { }
  • 示例:
ts
function print(x: string | number[]) { // 定义联合类型
  if (Array.isArray(x)) { // 类型缩小
    console.log("x", x.map(item => item * 2))
  } else {
    console.log("x", x)
  }
}

print([1, 2]);
print("abc")

export { }

1.3 交叉类型

1.3.1 前提

  • 请先将<<第二章:type 和 interface>> 看完,再返回这里,继续看交叉类型。

1.3.2 交叉类型

  • 交叉类型:表示两种(或多种类型)同时满足,并且交叉类型使用 & 符号。

注意⚠️:

  • type MyType = number & string 就是错误的,因为不可能有值既满足 number 类型又满足 string 类型。
  • ② 在实际开发中,交叉类型通常用来进行 type 类型别名的扩展类型,也可以对对象类型进行扩展使用
  • 示例:
ts
type Animal = {
  name: string
}

type Bear = Animal & { // 交叉类型,用于对 type 类型进行扩展类型
  honey: boolean
}

const bear: Bear = {
  name: "许大仙",
  honey: true
}

console.log(bear.name, bear.honey)

export {}
  • 示例:
ts
interface IPerson {
  name: string
}

interface IRun {
  running: () => void
}

const obj: IPerson & IRun = { // 交叉类型,对`对象类型`进行`扩展使用`
  name: "许大仙",
  running: () => {
    console.log("跑步....")
  }
}

console.log(obj.name)
obj.running()

export {}

第二章:type 和 interface

2.1 type(类型别名)

  • 之前,我们一直在类型注解中使用对象类型联合类型;但是,如果我们要在很多地方使用,就需要编写很多次,显得非常重复。

  • 此时,就可以使用 type(类型别名)来简化编写。

  • 示例:

ts
type ObjType = { // 类型别名,用来简化对象类型
  name: string
  age: number
  height?: number
}

const obj: ObjType = {
  name: "abc",
  age: 123
}

console.log(obj.name, obj.age, obj?.height)

export { }
  • 示例:
ts
type PointType = { // 类型别名,用来简化对象类型
  x: number
  y: number
  z?: number
}

function print(point: PointType) {
  console.log(point.x, point.y, point?.z)
}

print({ x: 1, y: 2 })

export { }
  • 示例:
ts
type IDType = number | string // 类型别名,用来简化联合类型

function print(id: IDType) {
  if (typeof id === "number") {
    console.log(id.toFixed(2))
  } else if (typeof id === "string") {
    console.log(id.toUpperCase())
  }
}

print(123)
print("abc")

export { }

2.2 interface(接口)

  • 对于对象类型,我们也可以使用 interface 接口来声明。

  • 示例:

ts
interface PointType { // 接口,用来简化对象类型
  x: number
  y: number
  z?: number
}

function print(point: PointType) {
  console.log(point.x, point.y, point?.z)
}

print({ x: 1, y: 2 })

export { }
  • 示例:
ts
interface ObjType { // 接口,用来简化对象类型
  name: string
  age: number
  height?: number
}

const obj: ObjType = {
  name: "abc",
  age: 123
}

console.log(obj.name, obj.age, obj?.height)

export { }

2.3 type VS interface

  • type 类型别名可以用来简化对象类型联合类型;但是,interface 接口只能用来简化对象类型
  • type 类别别名是通过交叉类型扩展类型的;而 interface 接口是通过 extends 关键字来扩展类型的,即:
ts
type Animal = {
  name: string
}

type Bear = Animal & {
  honey: boolean
}

const bear: Bear = {
  name: "许大仙",
  honey: true
}

console.log(bear.name, bear.honey)

export {}
ts
interface Animal  {
  name: string
}

interface Bear extends Animal{
  honey: boolean
}

const bear: Bear = {
  name: "许大仙",
  honey: true
}

console.log(bear.name, bear.honey)

export {}
  • type 类型别名不允许两个相同名称的别名同时存在;而 interface 接口是可以的,即:
ts
type Animal  = {
  title: string
}

type Animal  = { // 报错,标识符 Animal 重复定义
  age: number
}
ts
interface Animal {
  title: string
}

interface Animal   { // 没有报错
  age: number
}

温馨提示ℹ️:在实际开发中,如何选择?

  • ① 如果定义非对象类型,通常推荐使用 type 类型别名。
  • ② 如果定义对象类型,通常推荐使用 interface 接口。

第三章:类型断言和非空断言

3.1 类型断言(as)

image-20230728082104100

  • 我们知道,如果我们使用 document.getElementById("") 来获取元素,TS 只知道是某种 HTMLElement,并不知道具体的类型,可能是 HTMLCanvasElementHTMLLinkElementHTMLImageElement 等,即:
ts
const ele = document.getElementById("div")

export {}

image-20240126160500025

  • 但是,我们明确的知道,我们获取的就是 div 元素;此时,就可以使用类型断言(as) 来告知 TS 更准确的类型,以便做出更精确的提示,即:
ts
const ele = document.getElementById("div") as HTMLDivElement

export {}

image-20240126160658647

  • 注意⚠️:TypeScript 只允许转换为更具体或者更不具体的类型版本的类型断言,此规则是为了防止不可能的强制转换(慎用!!!)。
ts
const num: number = 12 
  
const age = num as string // 报错

export { }

image-20240126164158437

  • 根据提示,我们可以先 asknownany ,再 as其它类型,即:
ts
const num:number = 12 
  
const age = num as any as string // 不会报错

export { }

image-20240126164302268

3.2 非空断言(!)

  • 非空断言(!)用于在不进行任何显式检查的情况下,针对 undefined 类型或 null 类型不进行警告
ts
interface IPerson {
  name: string,
  age: number,
  friend?: {
    name: string
  }
}

const person: IPerson = {
  name: "张三",
  age: 18
}

// 访问属性的时候,我们可以使用可选链 ?.

console.log(person?.friend?.name)


// 但是,如果设置属性,可选链就没用了
person.friend?.name = "李四" // 报错


export { }

image-20240126165833433

  • 解决方案一:通过类型缩小来解决,即:
ts
interface IPerson {
  name: string,
  age: number,
  friend?: {
    name: string
  }
}

const person: IPerson = {
  name: "张三",
  age: 18
}

// 访问属性的时候,我们可以使用可选链 ?.

console.log(person?.friend?.name)

// 解决方案一:通过类型缩小
if (person.friend) { // 通过类型缩小来设置属性,不会报错
  person.friend.name = "李四"
}


export { }

image-20240126170002156

  • 解决方案二:通过非空断言,即:
ts
interface IPerson {
  name: string,
  age: number,
  friend?: {
    name: string
  }
}

const person: IPerson = {
  name: "张三",
  age: 18
}

// 访问属性的时候,我们可以使用可选链 ?.

console.log(person?.friend?.name)

// 解决方案二:通过非空断言
person!.friend!.name = "李四" // 通过非空断言来设置属性,不会报错,只是压制警告而已!!!

export { }

image-20240126170134338

注意⚠️:在实际开发中,并不推荐使用非空断言(!)来压制警告,因为可能会造成代码运行错误,慎用!!!

第四章:字面量类型和类型缩小

4.1 字面量类型(文本类型)

4.1.1 回顾字面量

  • 在 JavaScript 中,字面量是一种表示固定值的语法。字面量可以在代码中直接使用,并且不需要通过变量或函数来表示。
  • 字面量可以表示各种数据类型,包括:字符串、数字、布尔值、数组、对象和正则表达式等。
  • 以下是一些常见的 JavaScript 字面量的示例:
    • ① 字符串字面量:'Hello World'"JavaScript"
    • ② 数字字面量:1233.14
    • ③ 布尔字面量:truefalse
    • ④ 数组字面量:[1, 2, 3]['apple', 'banana', 'orange'].
    • ⑤ 对象字面量:{name: 'John', age: 25}
    • ⑥ 正则表达式字面量:/pattern/
  • 使用字面量可以方便地创建和表示各种类型的值,而不需要显式地使用构造函数或其他语法结构。

4.1.2 字面量类型

  • 在 TS 中,对于字符串字面量数字字面量以及布尔字面量可以作为类型注解(TS 会自动推断),如:
ts
// 字面量类型的基本使用
const a = "abc"
const b = 1
const c = true

export { }

image-20240126171617310

  • 当然,你也可以使用 let 定义变量的时候,设置字面量类型,即:
ts
// 字面量类型的基本使用
const a = "abc"
const b = 1
const c = true

// 字面量类型的基本使用
let d: "abc" = "abc"
let e: 1 = 1
let g: true = true

export { }

image-20240126171752451

  • 就其本身而言,普通的字面量类型用处不是很大,但是一旦和联合类型结合,就可以模拟出枚举的作用,即:
ts
// 字面量类型配合联合类型可以模拟出枚举的作用
type Direction = 'left' | 'right' | 'up' | 'down'

function move(direction: Direction) {
  switch (direction) {
    case 'left':
      console.log('left')
      break
    case 'right':
      console.log('right')
      break
    case 'up':
      console.log('up')
      break
    case 'down':
      console.log('down')
      break
  }
}

move('left') // 参数只能是 'left' | 'right' | 'up' | 'down' 中的一个

export { }

image-20240129094559529

  • 当然,字面量类型也可以和其它类型结合使用,即:
ts
interface Options {
  width: number;
}
function configure(x: Options | "auto") {
  // ...
}
configure({ width: 100 });
configure("auto");
configure("automatic"); // 报错

export {}

image-20240129101007759

4.1.3 字面量类型推理(推断)

  • 当我们使用对象初始化变量的时候,TS 假设该对象的属性稍后可能会变化,如:
ts
const obj = {
  name: '许大仙',
  age: 18
}

if (true) { 
  obj.age = 19 
}

export { }

image-20240129100308090

注意⚠️:TS 不会假设 age 赋值为 19 是错误的,因为 TS 推断出 obj 的类型是 {name:string , age: number},所以 age 赋值为什么数值都是可以的。

  • 这个规则同样适用于字面量类型,如:
ts
function request(url: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD') {
  // ...
}

const req = {
  url: 'http://localhost:3000/api/v1/users/1',
  method: 'GET'
}

request(req.url, req.method) // 报错

export { }

image-20240129101149656

注意⚠️:之所以报错的原因就在于 TS 将 req 对象推断为 {url: string,method:string}类型,而 method 要求是 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' 这些字面量类型,因为你可能会将 method 设置为 'A',就不是'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' 这些字面量类型中的一种了。

  • 解决方案一:使用 as 类型断言,即:
ts
function request(url: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD') {
  // ...
}

const req = {
  url: 'http://localhost:3000/api/v1/users/1',
  method: 'GET'
}

request(req.url, req.method as 'GET') // 使用 as 类型断言,解决报错问题。

export { }

image-20240129101520861

  • 解决方式二:使用 as const 将对象转换为字面量类型,即:
ts
function request(url: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD') {
  // ...
}

const req = {
  url: 'http://localhost:3000/api/v1/users/1',
  method: 'GET'
} as const // 使用 `as const` 将对象转换为字面量类型,解决报错问题

request(req.url, req.method ) 

export { }

image-20240129101646267

4.2 类型缩小(Type Narrowing )

4.2.1 概述

  • 对于下面的函数,即:
ts
/**
 * 
 * @param padding 如果 padding 是 number 类型,则将 padding 作为 input 要预留的空格数;
 *                如果 padding 是 string 类型,则将 padding 前面加上 input 
 * @param input 
 */
function padLeft(padding: number | string, input: string): string {
  throw new Error("Not implemented yet!");
}

export {}
  • 如果我们这么实现,即:
ts
/**
 * 
 * @param padding 如果 padding 是 number 类型,则将 padding 作为 input 要预留的空格数;
 *                如果 padding 是 string 类型,则将 padding 前面加上 input 
 * @param input 
 */
function padLeft(padding: number | string, input: string): string {
  return "".repeat(padding) + input; // 报错
}

export {}

image-20240129105941177

注意⚠️:之所以报错的原因很简单,repeat() 函数只接受 number ,而 padding 参数可能是 number 或 string 类型,当然会报错!!!

  • 在 JS 中,我们为了代码的健壮性以及正确性,通常会使用 typeof 来判断函数参数的类型,在 TS 中也不例外,如:
ts
/**
 * 
 * @param padding 如果 padding 是 number 类型,则将 padding 作为 input 要预留的空格数;
 *                如果 padding 是 string 类型,则将 padding 前面加上 input 
 * @param input 
 */
function padLeft(padding: number | string, input: string): string {
  if (typeof padding === 'number') { // 使用 typeof 类型缩小,将类型由 number | string 变为 number
    return " ".repeat(padding) + input;
  }
  return padding + input;
}

export {}

image-20240129110333896

4.2.2 typeof 类型防护(Type Guards)

  • JavaScript 中支持 typeof 运算符,它可以提供运行时值的类型;当然,TS 中也支持,并返回一组特定的字符串:
    • "string"
    • "number"
    • "bigint"
    • "boolean"
    • "symbol"
    • "undefined"
    • "object"
    • "function"

注意⚠️:

  • typeof xxx 不返回字符串 null ;typeof null 返回的是 "object",这绝对是 JS 语言的 Bug;TS 为了兼容 JS,也保留下来了。
  • 换言之,对于 null 的处理,需要单独判断,后面讲解!!!
  • 示例:
ts
type IDType = string | number

function printID(id: IDType) {
  if (typeof id === 'string') {
    console.log(id.length, id.toUpperCase())
  } else {
    console.log(id, id.toFixed(2))
  }
}

printID(123)
printID('123')

export { }

image-20240129111558931

  • 示例:
ts
function printAll(strs: string | string[] | null) {
  if (typeof strs === "object") {
    for (const s of strs) { // 报错,因为 typeof null 也是 'object'
      console.log(s);
    }
  } else if (typeof strs === "string") {
    console.log(strs);
  } else {
    // do nothing
  }
}

export { }

image-20240129111742716

4.2.3 平等缩小(Equality Narrowing)

  • 在 TS 中,我们可以使用if 语句、switch 语句和相等检查(如:=== !==, ==, and !=)来缩小类型范围。

  • 示例:

ts
// 字面量类型配合联合类型可以模拟出枚举的作用
type Direction = 'left' | 'right' | 'up' | 'down'

function move(direction: Direction) {
  switch (direction) {
    case 'left':
      console.log('left')
      break
    case 'right':
      console.log('right')
      break
    case 'up':
      console.log('up')
      break
    case 'down':
      console.log('down')
      break
  }
}

move('left') // 参数只能是 'left' | 'right' | 'up' | 'down' 中的一个

export {}
  • 示例:
ts
function printAll(strs: string | string[] | null) {
    if (Array.isArray(strs)) {
      for (const s of strs) {
                       
        console.log(s);
      }
    } else if (typeof strs === "string") {
      console.log(strs);
                   
    }
}

printAll(null);

export {}

4.2.4 in 操作符

  • JavaScript 有一个运算符 in ,如果指定的属性在指定的对象或其原型链中,则 in 运算符返回 true

  • 当然,TS 也支持 in 运算符。

  • 示例:

ts
type ISwim = {
  swim: () => void
}

type IRun = {
  run: () => void
}


function move(animal: ISwim | IRun) {
  if ('swim' in animal) {
    animal.swim()
  } else if ('run' in animal) {
    animal.run()
  }
}


const fish = {
  swim: () => console.log('fish swim')
}


const dog = {
  run: () => console.log('Dog run')
}


move(fish)
move(dog)


export { }

4.2.5 instanceof 运算符

  • JavaScript 有一个运算符 instanceof ,用来判断某个对象是否是某个类的实例。

  • 当然,TS 也支持 instanceof 运算符。

  • 示例:

ts
function print(x: Date | string) {
  if (x instanceof Date) {
    console.log(x.toLocaleString())
  } else {
    console.log(x.toUpperCase())
  }
}

print(new Date())
print('x')

export { }

第五章:函数的类型和函数签名

5.1 函数的类型

5.1.1 函数类型表达式(Function Type Expressions)

  • 在 JavaScript 中,函数是头等公民;换言之,函数可以写到任意位置。
  • 对于函数而言,我们经常使用 function 关键字来声明函数,并在函数体内部封装功能实现,即:
ts
/**
 * 函数最主要的目的就是封装功能
 * @param num number 类型
 * @returns number 类型
 */
function getNum(num: number): number {
  return num
}

const res = getNum(10)
console.log(res)

export { }

image-20240129133208618

  • 但是,函数还有函数表达式以及箭头函数的写法,即:
ts
/**
 * 函数最主要的目的就是封装功能
 * @param num number 类型
 * @returns number 类型
 */
const getNum = (num: number): number => {
  return num
}

const res = getNum(10)
console.log(res)

export { }

image-20240129133332502

注意⚠️:

  • ① 在 JS 中,函数也是对象,即函数也是有类型的;根据 TS 的提示,我们知道,上述示例中,getNum 的类型是 (num: number) => number,这就是函数的类型,官方文档称为函数类型表达式
  • ② 函数类型表达式的语法:(参数列表) => 返回值,如果返回值没有,就使用 void ,如:(a: string,b: number) => void,类似于箭头函数的语法。
  • ③ 函数类型表示式中的 参数 必须写,如果没有写,就是 any ,即:(string,number) => void 表示的就是 (string: any,number: any)=> void
  • 此时,我们也可以手动标注函数的类型注解(函数类型表达式),即
ts
/**
 * 函数最主要的目的就是封装功能
 * @param num number 类型
 * @returns number 类型
 */
const getNum: (num: number) => number = (num: number): number => {
  return num
}

const res = getNum(10)
console.log(res)

export { }

image-20240129133754358

  • 但是,不觉得这样看起来很不直观吗?可以使用 type 类型注解来简化,即:
ts
type getNumType = (num: number) => number
/**
 * 函数最主要的目的就是封装功能
 * @param num number 类型
 * @returns number 类型
 */
const getNum: getNumType = (num: number): number => {
  return num
}

const res = getNum(10)
console.log(res)

export { }

image-20240129133944764

  • 在函数表达式的写法中,如果标注了函数的类型注解(函数类型表达式),函数的参数类型和返回值类型可以省略,由 TS 自动推断,即:
ts
type getNumType = (num: number) => number
/**
 * 函数最主要的目的就是封装功能
 */
const getNum: getNumType = (num) => {
  return num 
}

const res = getNum(10)
console.log(res)

export { }

image-20240129135609520

5.1.2 函数类型表达式的应用

  • 需求:通过回调函数实现计算器功能。

  • 示例:

js
function calc(num1, num2, callbackFn) {
  return callbackFn(num1, num2);
}

const num1 = 1
const num2 = 2

let result = calc(num1, num2, (x, y) => x + y)
console.log('加法', result);

result = calc(num1, num2, (x, y) => x - y)
console.log('减法', result);

result = calc(num1, num2, (x, y) => x * y)
console.log('乘法', result);

result = calc(num1, num2, (x, y) => x / y)
console.log('除法', result);
  • 示例:
ts
function calc(num1: number, num2: number, callbackFn: (num1: number, num2: number) => number) {
  return callbackFn(num1, num2);
}

const num1 = 1
const num2 = 2

let result = calc(num1, num2, (x, y) => x + y)
console.log('加法', result);

result = calc(num1, num2, (x, y) => x - y)
console.log('减法', result);

result = calc(num1, num2, (x, y) => x * y)
console.log('乘法', result);

result = calc(num1, num2, (x, y) => x / y)
console.log('除法', result);

export { }
  • 示例:
ts
type CallbackFnType = (num1: number, num2: number) => number

function calc(num1: number, num2: number, callbackFn: CallbackFnType) {
  return callbackFn(num1, num2);
}

const num1 = 1
const num2 = 2

let result = calc(num1, num2, (x, y) => x + y)
console.log('加法', result);

result = calc(num1, num2, (x, y) => x - y)
console.log('减法', result);

result = calc(num1, num2, (x, y) => x * y)
console.log('乘法', result);

result = calc(num1, num2, (x, y) => x / y)
console.log('除法', result);

export { }

5.2 函数签名

5.2.1 调用签名(Call Signatures)

  • 在 JS 中,函数即对象,所以函数也可以添加属性方法,即:
js
function person(){
    
}
person.country = "中国"
person.study = function () {
    console.log("学生学习")
}

console.log(person.country)
person.study()
  • 当然,在 ES5 中,我们也会使用函数作为构造函数来模拟类,即:
js
function Student(name, age) {
    this.name = name
    this.age = age
    this.eating = function () {
        console.log(`${this.name}正在吃饭~`)
    }
}

// 函数是对象,也是可以添加属性和方法;并且,在面向对象编程中,通过函数名添加的属性和方法,称之为静态属性和静态方法
Student.country = "中国"
Student.study = function () {
    console.log("学生学习")
}

// 调用静态属性和静态方法
console.log(Student.country)
Student.study()

// 实例化对象
var stu = new Student("许大仙", 18)
// 对象调用属性和方法
console.log(stu.name)
console.log(stu.age)
stu.eating()
  • 在 TS 中,函数除了可以调用(功能)以外还具有属性;此时,函数表达式语法就不行了,函数表达式语法只能体现函数的功能,不能体现函数的属性。
  • 如果我们要体现函数的属性,就需要使用对象类型来描述函数,即:
ts
// 调用签名
type IBar = { // 对象类型
  description: string // 属性描述,不要使用 name 来测试,因为对象函数而言 name 是 readonly 属性
  age?: number // 属性描述
  (num1: number, num2: number): number // 自身功能描述,即函数本身的功能,用来封装功能~
}

const bar: IBar = (num1, num2) => {
  return num1 + num2
}

// 设置属性
bar.description = "许大仙"
bar.age = 18

// 获取属性
console.log(bar.description)
console.log(bar?.age)

// 调用函数(函数本身的功能不就是封装功能吗)
const result = bar(1, 2)
console.log(result)

export { }

注意⚠️:

  • 调用签名函数类型表达式相比,语法略有不同:在参数列表和返回类型之间使用,而不是 =>:
  • ② 很好理解,毕竟是用对象类型来描述调用签名。
  • 调用签名语法和函数类型表达式语法最大的区别就在于:调用签名可以用来描述带属性的函数
  • 当然,调用签名中也可以添加方法的,即:
ts
// 调用签名
type IBar = { // 对象类型
  description: string // 属性描述
  age?: number // 属性描述
  eat: (food: string) => void // 属性描述
  (num1: number, num2: number): number // 自身功能描述,即函数本身的功能,用来封装功能~
}

const bar: IBar = (num1, num2) => {
  return num1 + num2
}

// 设置属性
bar.description = "许大仙"
bar.age = 18
bar.eat = (food: string) => {
  console.log(`吃${food}`)
}

// 获取属性
console.log(bar.description)
console.log(bar?.age)
bar.eat("苹果")

// 调用函数(函数本身的功能不就是封装功能吗)
const result = bar(1, 2)
console.log(result)

export { }

温馨提示ℹ️:实际开发中,如何使用?

  • ① 如果只是描述函数类型本身(体现函数的封装功能),即函数可以被调用,就使用函数类型表达式
  • ② 如果在描述函数作为对象被调用,并且还有其它属性其它方法的时候,就使用调用签名

5.2.2 构造签名(Construct Signatures)

  • 前面,我们也解释下,在 ES5 中,函数还可以作为构造函数(用于模拟)来创建对象
  • 但是,在 TS 中,如果需要创建对象,必须通过 class 关键字,也可以使用构造签名来描述 constructor 构造器接受参数类型返回的类型(类)

注意⚠️:

  • ① 在 TS 中,构造签名本身并不能用于创建对象实例,而是用来描述constructor构造器应该接受参数类型返回的类型(类)
  • ② 换言之,在 TS 中,构造签名通常配合工厂函数创建对象
  • 示例:
ts
interface Point {
  x: number;
  y: number;
}

type PointConstructor = {
  new (x: number, y: number): Point // 构造签名
}

class Point2D implements Point {
  constructor(public x: number, public y: number) {}
}

function createPoint(ctor: PointConstructor, x: number, y: number): Point { // 工厂函数
  return new ctor(x, y);
}

let point: Point = createPoint(Point2D, 3, 4);
console.log(point); 

export { }

5.2.3 可选参数(Optional Parameters)

  • 在 JavaScript 中的函数通常采用可变数量参数,如:
js
function bar(n: number) {
  console.log(n.toFixed()) // toFixed() 没有参数,即 0 个参数
  console.log(n.toFixed(2)) // toFixed(2) 2 个参数
}

bar(2)

export {}
  • 在 TS 中也是类似的,我们可以使用 ? 将参数标记为可选,即:
ts
function bar(n?: number) { // 可选参数
  console.log(n && n.toFixed()) 
  console.log(n && n.toFixed(2)) 
}

bar(2)

export {}

注意⚠️:在 TS 中的可选参数必须在必选参数之后的位置;否则,将会编译报错!!!

  • 其实,上述示例虽然将 n 指定为 number 类型,其实是 number | undefined 联合类型,即如果不传递参数就会取值为 undefined,即:

image-20240130094447808

注意⚠️:既然上述示例中的可选参数 n 的类型是 number | undefined联合类型,那么在使用的时候,就需要使用类型缩小,否则将会编译报错!!!

5.2.4 默认参数(Default Parameters)

  • 在 JavaScript 中,函数的默认参数允许在没有值undefined 被传入的时候使用默认形参,即:
js
function multiply(a, b = 1) {
  return a * b;
}

console.log(multiply(5, 2));
// Expected output: 10

console.log(multiply(5));
// Expected output: 5
  • 同理,在 TS 也是支持默认参数的,即:
ts
function multiply(a: number, b = 1) { // 默认参数没有必要编写类型注解,因为可以根据默认参数的值自动推断
  return a * b;
}

console.log(multiply(5, 2));
// Expected output: 10

console.log(multiply(5));
// Expected output: 5

export { }

5.2.5 回调函数中的可选参数(Optional Parameters in Callbacks)

  • 在 JavaScript 中,我们经常会使用高阶函数(回调函数),如:
js
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

nums.forEach((item) => {
  console.log(item)
})

nums.forEach((item, index) => {
  console.log(item, index)
})

nums.forEach((item, index, arr) => {
  console.log(item, index, arr)
})
  • 了解了可选参数和函数类型表达式之后,我们可能会这么编写,即:
ts
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

nums.forEach((item: number, index: number, arr?: number[]) => {
  console.log(item, index, arr)
})

export { }
  • 其实,是没有必要的,因为如果强制加上类型,反而引发 TS 发出实际上不可能的错误,即:
ts
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

nums.forEach((item: number, index: number, arr?: number[]) => {
  console.log(item, index, arr[0])
})

export { }

image-20240130100035250

  • 所以,在 TS 中的回调函数中的形参没有必要加上类型注解(包括可选参数),因为 TS 会自动推断形参的类型,即:
ts
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

nums.forEach((item, index, arr) => {
  console.log(item, index, arr[0])
})

export { }

image-20240130100349938

5.2.6 剩余参数(Rest Parameters )

  • 在 JavaScript 中,剩余参数语法允许我们将一个不定量的参数表示为一个数组,即:
js
function sum(...theArgs) {
  let total = 0;
  for (const arg of theArgs) {
    total += arg;
  }
  return total;
}

console.log(sum(1, 2, 3));
// Expected output: 6

console.log(sum(1, 2, 3, 4));
// Expected output: 10
  • 当然,在 TS 中也支持剩余参数,即:
ts
function sum(...theArgs: number[]) {
  let total = 0;
  for (const arg of theArgs) {
    total += arg;
  }
  return total;
}

console.log(sum(1, 2, 3));
// Expected output: 6

console.log(sum(1, 2, 3, 4));
// Expected output: 10

export {}

第六章:函数的重载和函数中 this 类型

6.1 函数的重载(Function Overloads)

  • 需求:要求编写一个函数,希望可以对参数类型字符串数字类型进行相加。

注意⚠️:参数的类型要么都是字符串,要么都是数字类型。

  • 我们可能会想到联合类型,即:
ts
function add(a: number | string, b: number | string) {
  if (typeof a === 'string' && typeof b === 'string') {
    return a + b
  }
  if (typeof a === 'number' && typeof b === 'number') {
    return a + b
  }
}

export { }
  • 即使我们使用了类型缩小来处理,但是还是有问题的,因为返回值的类型可能为 undefined ,即:

image-20240130111504435

  • 此时,就可以使用函数重载来解决,如何编写?
    • ① 可以先编写不同的重载签名来表示函数可以以不同的方式进行调用
    • ② 再编写实现签名,其实就是编写一个通用的函数实现

注意⚠️:实现签名必须和重载签名兼容。

  • 所以,对应的功能实现如下:
ts
function add(a: number, b: number): number // 重载签名 
function add(a: string, b: string): string // 重载签名 

function add(a: any, b: any): any { // 实现签名
  return a + b
}

let result1 = add(1, 2)
console.log(result1)

let result2 = add('1', '2')
console.log(result2)


export { }

image-20240130112049719

6.2 函数中 this 类型(this in a Function)

6.2.1 概述

  • 默认情况下,如果没有对 TS 进行任何配置,那么函数的 this 可以会被 TS 推断为 any 类型,即:
ts
// ① 对象中的函数的 this
const obj = {
  name: '许大仙',
  age: 18,
  studing: function () {
    // 默认情况下,是 any 类型
    console.log(this.name + '正在学习...')
  }
}

obj.studing()

// ② 普通的函数
function foo() {
  // 默认情况下,是 any 类型
  console.log(this)
}

foo()

export { }

image-20240130135144087

  • 但是,使用 any 往往会破坏使用 TS 的目的,因为使用 any 只是让 TS 回到了普通的 JavaScript 体验,即:
ts
// ① 对象中的函数的 this
const obj = {
  name: '许大仙',
  age: 18,
  studing: function () {
    // 默认情况下,是 any 类型
    console.log(this.name + '正在学习...')
  }
}

obj.studing()
obj.studing.call({}) // 这里的 this 就不应该是 any 类型,而应该是 {},存在安全隐患!!!

// ② 普通的函数
function foo() {
  // 默认情况下,是 any 类型
  console.log(this)
}

foo()

export { }

image-20240130135824644

注意⚠️:

  • 之所以这样设计,有因为有些人希望有一种非常宽松的选择可以将 TS 加入到项目中,并且仅仅帮助他们来验证程序的某些部分,这也是 TS 的默认体验,即推断往往采用最宽松的类型,并且不检查潜在的 null 或 undefined 。
  • 但是,如果我们希望 TS 尽可能的帮助我们严格,那么就需要开启严格模式了,这样 TS 会帮助我们对类型隐式推断为 any 的任何变量发出警告,或者对潜在的忘记处理的 null 或 undefined 发出警告。
  • 了解即可,因为 Vue 3.x 的 Composition API 和 React 18 的 Hook API 已经很少使用 this 了。

6.2.2 this 的编译选项

  • 我们可以通过 tsconfig.json 文件来开启严格模式,即:
shell
tsc --init

  • 其中,tsconfg.json 的文件内容,如下所示:
json
{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Projects */
    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */

    /* Language and Environment */
    "target": "es2016",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
    // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */

    /* Modules */
    "module": "commonjs",                                /* Specify what module code is generated. */
    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
    // "moduleResolution": "node10",                     /* Specify how TypeScript looks up a file from a given module specifier. */
    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
    // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
    // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
    // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
    // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
    // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
    // "resolveJsonModule": true,                        /* Enable importing .json files. */
    // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

    /* JavaScript Support */
    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */

    /* Emit */
    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
    // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
    // "removeComments": true,                           /* Disable emitting comments. */
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
    // "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */
    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
    // "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */

    /* Interop Constraints */
    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */

    /* Type Checking */
    "strict": true,                                      /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
    "noImplicitThis": true, /* 注意打开此处 */                          /* Enable error reporting when 'this' is given the type 'any'. */
    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */

    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  }
}

注意⚠️:

  • Implicit 翻译为中文是隐藏隐式的意思。
  • ② 默认情况下,"noImplicitThis": true是注释掉的,即 TS 允许 this 推断为 any 类型。
  • ③ 一旦打开 "noImplicitThis": true选项,则表示项目中的 this 不允许出现隐式的 any 类型,即 TS 会根据上下文环境推导 this ,如果不能正确的推导,就报错;此时,就需要我们明确的指定 this 的类型。
  • ④ 开启"noImplicitAny": true选项,则表示项目中不允许隐式的 any 出现, 即 TS 会根据上下文环境推导变量的类型,如果推导出是 any 类型,就报错。
  • ⑤ 开启"strictNullChecks": true选项,是用来对潜在的、忘记处理的 null 或 undefined 发出警告
  • 此时,我们可以通过 VSCode 查看:
ts
// ① 对象中的函数的 this
const obj = {
  name: '许大仙',
  age: 18,
  studing: function () {
    // 默认情况下,是 any 类型;但是,开启 "noImplicitAny": true 之后,TS 会推断为 obj 对象
    console.log(this.name + '正在学习...')
  }
}

obj.studing()
// obj.studing.call({}) // 这里的 this 就不应该是 any 类型,而应该是 {},存在安全隐患!!!

// ② 普通的函数
function foo() {
  // 默认情况下,是 any 类型
  console.log(this)
}

foo()

export { }

image-20240130144140102

6.2.3 指定 this 的类型

  • 在开启 "noImplicitThis": true选项之后,我们需要指定 this 的类型。
  • 如何指定?此时将 this 作为函数的第一个参数类型
    • 函数的第一个参数,可以根据函数被调用的情况,用来声明 this 的类型(this 名称的固定的);
    • 在后续调用函数传入参数的时候,从第二个参数开始传递,并且 this 参数会在编译后被擦除;
  • 此时,可以这么修改:
ts
// ① 对象中的函数的 this
const obj = {
  name: '许大仙',
  age: 18,
  studing: function () {
    // 默认情况下,是 any 类型;但是,开启 "noImplicitThis": true 之后,TS 会推断为 obj 对象
    console.log(this.name + '正在学习...')
  }
}

obj.studing()
// obj.studing.call({}) // 这里的 this 就不应该是 any 类型,而应该是 {},存在安全隐患!!!

// ② 普通的函数
function foo(this: typeof globalThis, name: string) { // 第一个参数必须是 this ,并且用来声明 this 的类型
  console.log(this, name)
}

foo.call(globalThis, "许大仙") // 之所以需要这么写,是因为 TS 会转换为 JS 的严格模式,严格模式中是不能这么写 window.xxx()

export { }

image-20240130150245794

  • 当然,对于 TS 根据上下文推导 this 类型(并不一定推导正确),为了程序的正确性,我们也需要自己手动指定,即:
ts
// ① 对象中的函数的 this
const obj = {
  name: '许大仙',
  age: 18,
  studing: function (this: {}) {
    // 默认情况下,是 any 类型;但是,开启 "noImplicitThis": true 之后,TS 会推断为 obj 对象
    console.log(this + '正在学习...')
  }
}

// obj.studing()
obj.studing.call({}) // 这里的 this 就不应该是 any 类型,而应该是 {},存在安全隐患!!!

// ② 普通的函数
function foo(this: typeof globalThis, name: string) { // 第一个参数必须是 this ,并且用来声明 this 的类型
  console.log(this, name)
}

foo.call(globalThis, "许大仙")

export { }

6.2.4 this 类型相关内置工具

  • 在 JavaScript 中,typeof 函数的值是 function ,表示函数对象,即:
ts
function foo() {

}

let res = typeof foo // 注意,使用 let 接受,意味着获取的是 foo 的值类型 

console.log(res) // function

export { }

image-20240130152259087

  • 但是,在 TS 中,我们也可以通过 type 来表明获取的是函数的类型(函数也是一种对象,即函数也有自己的类型),即:
ts
function foo(name: string,age: number): void  {

}

type res = typeof foo // 通过 type 来声明获取的是 foo 函数的类型,而不是 foo 函数的值类型,因为 type 会在编译的时候擦除

export { }

image-20240130152939097

  • 但是,有的时候我们只关心函数的 this 类型,此时就需要使用 ThisParameterType<Type> 泛型工具了,即:提取函数类型的 this 参数的类型,如果函数类型没有 this 参数,则提取 unknown
ts
function foo(this: typeof globalThis, name: string, age: number): void {

}

type thisType = ThisParameterType<typeof foo>


export { }

image-20240130153519254

  • 但是,有的时候,我们需要从 Type 中删除 this 参数类型,就可以使用 OmitThisParameter<Type>泛型工具,即:
ts
function foo(this: typeof globalThis, name: string, age: number): void {

}

type thisType = ThisParameterType<typeof foo>

type x = OmitThisParameter<typeof foo>

export { }

image-20240130161317143

Released under the MIT License.