7、TS内容学习总结

前期背景

TS是什么?

TS是JS的类型的超集,支持ES6语法,支持面向对象编程的概念,如类、接口、继承、泛型等。

js和java区别

  • JS:解释型,弱类型,动态语言
  • Java:编译型,强类型,静态语言

解释:

  • 解释型:我们写的代码,无需进行编译,直接运行,也需要一个翻译器,一遍翻译一边执行
  • 编译型:人类写的是英语,机器不认识,需要一个编译器,将人们写的语言转换成机器识别的语言,这个过程叫编译
  • 弱类型:声明变量时无需指定类型
  • 强类型:声明变量时必须指定类型
  • 动态语言:在代码执行的过程中可以动态添加对象的属性
  • 静态语言:不允许在执行过程中随意添加属性

结论:js的特点是灵活、高效,很短的代码就能实现复杂功能,缺点是没有代码提示,容易出错且编辑工具不会给任何提示,而Java则相反。

TS可以简单理解为将JavaScript转变为Java一类语言的过程

为什么学习TS

  • 从编译语言的动静来区分,TS属于静态类型的编程语言,JS属于动态类型的编程语言
    • 静态类型:编译期做类型检查
    • 动态类型:执行期做类型检查
  • 对于JS来说,需要等到代码真正去执行的时候才能发现错误(晚), 对于TS来说,在代码编译的时候(代码执行前)就可以发现错误(早)

并且,配合VSCode等开发工具,TS可以提前到在编写代码的同时就发现代码中的错误,减少找Bug,改Bug时间,另外还支持代码提示

Vue3的源码使用TS重写,Angular默认支持TS、React与TS完美配合,TypeScript已经成为大中型前端项目的首选变成语言。
注意:Vue2对TS的支持不好~

TypeSscript类型

类型注解总结:

  • 将来不能将其他类型的值赋予这个变量
  • 代码有提示,在变量后面.可以直接看到当前类型所支持的所有属性和方法

可以将TS中的常用基础类型细分为两类:

  • JS已有类型
    • 原始类型,简单类型(number、string、boolean、null、undefined)
    • 复杂数据类型(数组、对象、函数等)
  • TS新增类型
    • 联合类型
    • 自定义类型(类型别名)
    • 接口
    • 元组
    • 字面量类型
    • 枚举
    • void
    • any
    • never
    • 名字空间

 


 

1、基础类型

// 字符串类型
let a:string = '123';

// 数字类型
let num: number = 123;

// 与 void 的区别是,undefined 和 null 是所有类型的子类型。
// 也就是说 undefined 类型的变量,可以赋值给 string 类型的变量:
//这样是没问题的
let test: null = null
let num2: string = "1"
num2 = test
 

// 在 TypeScript 中,任何类型都可以被归为 any 类型。
// 这让 any 类型成为了类型系统的顶级类型(也被称作全局超级类型)。
// 没有强制限定哪种类型,随时切换类型都可以 我们可以对 any 进行任何操作,不需要检查类型
let anys:any = 123
anys = '123'
anys = true

// unknown 类型只能被赋值给 any 类型和 unknown 类型本身。
let value: unknown;

let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error

2、数组类型

let arr: number[] = [123]

//这样会报错定义了数字类型出现字符串是不允许的
let arr: number[] = [1,2,3,'1']

//数字类型的数组
var arr: number[] = [1, 2, 3];
//字符串类型的数组
var arr2: string[] = ["1", "2"];


// 数组泛型,规则 Array<类型>
let arr:Array<number> = [1, 2, 3, 4, 5];

// 多维数组
let data:number[][] = [[1,2], [3,4]];

//任意类型的数组
var arr3: any[] = [1, "2", true];

// 希望数组里面可以存数字或字符串
// 联合类型 |
let arr4:(number | string)[] = [1, 2, 4, '2323'];
// 注意:| 的优先级较低,需要用()包裹提升优先级
// 一旦使用联合类型,说明 arr 中存储的既可能是number 也可能是 string,所以会丢失一部分提示信息

// 定时器?

3、类型别名

目标:能够使用类型别名给类型起别名
使用场景:当同一类型(复杂)被多次使用时,可以通过type定义类型别名(推荐使用大写字母开头),简化该类型的使用。

// 希望 N 个数组里面可以存数字或字符串
// 方法1:里面上面的联合类型
// 方法2:类型别名
type ArrType = (number | string)[]

let arr1: ArrType = ['1', 2, 4, '2323'];
let arr2: ArrType = [1, '2', 4, '2323'];
let arr3: ArrType = [1, 2, '4', '2323'];


// 灵活度很高,可以随意搭配组合使用
type ItemType = number | string
let arr4: ItemType[] = [1, 2, '3', '123'];
let arr5: ItemType = '23'

// 总结:将一组类型存储到【变量】里,用 type 来声明这个特殊的【变量】

// 玩花活
type s = string
type n = number

let str2: s = '123'
let num2: n = 123

4、函数类型

函数的类型实际上指的是:函数参数返回值的类型
为函数指定类型的两种方式:

  • 单独指定参数、返回值的类型
  • 同时指定参数、返回值的类型
// ts 要求我们必须给参数定义类型,而返回值它会自动推断
// 函数声明
function add(a: number, b: number) {
	return a + b;
}

// 箭头函数,ts中的箭头函数必须是小括号
const sub = (a: number, b: number): number => {
	return a + b;
}

//通过?表示该参数为可选参数
const fn = (name: string, age?:number): string => {
    return name + age
}
fn('张三')

// 函数参数的默认值
const fn = (name: string = "我是默认值"): string => {
    return name
}
fn()

// 函数的类型别名
type FnType = (a: number, b: number) => number

// 函数的类型别名通常是给箭头函数/函数表达式使用,不会给函数声明使用

const fn: FnType = function(a, b) {
  return a + b
}


函数重载

// 重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。
//如果参数类型不同,则参数类型应设置为 any。
// 参数数量不同你可以将不同的参数设置为可选。

function fn(params: number): void
 
function fn(params: string, params2: number): void
 
function fn(params: any, params2?: any): void {
    console.log(params)
    console.log(params2)
}
 
fn(123)
fn('123',456)

5、void类型 && never类型

void

如果函数没有返回值,那么,在TS的类型中,函数返回值类型为void

const sayHello = (content: string): void => {
  console.log(content)
}

// 但,如果指定返回值类型为 undefined,
// 此时,函数体中必须显示的 return undefined 才可以
const addN = (): undefined => {
  return undefined
}

never

never类型代表从不会出现的值,注意:never的变量只能被never类型所赋值。
never类型一般用来指定那些总是抛出异常、无线循环

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
	throw new Error(message);
}

6、可选参数

  • 使用函数实现某个功能时,参数可以传也可以不传。这种情况下,在给函数参数指定类型时,就用到可选参数。在可选可不传的参数名称后面添加?
  • 注意:可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数。
const printP = (name: string, age?: number): void => {
  console.log(name, age)
}

printP('哆啦A梦', 18)
printP('波多野结衣')

7、对象

JS中的对象是由属性和方法构成的,而TS对象的类型就是在描述对象的结构(有什么类型的属性和方法)。
调用对象时可以直接提示有哪些属性和方法

// ts 就像咋写注释,以前写的注释是给程序员看的,ts 写的类型是给编辑器看的,程序员也可以看
let obj: {
  name: string,
  age: number,
  sayHi: (content: string) => void
} = {
  name: '哆啦A梦',
  age: 18,
  sayHi(content) {
    console.log(content)
  }
}

// 类型别名用法

// 对象类型
type Person = {
  name: string,
  age: number,
  sayHi: (content: string) => void 
}

let obj1: Person = {
  name: '哆啦A梦',
  age: 18,
  sayHi(content) {
    console.log(content)
  }
}

方法类型的两种定义形式

// 方法的类型也可以使用箭头函数形式
type Person = {
  greet: (name: string) => void
  // greet(name: string) => void
}
let person: Person = {
  greet(name) {
    console.log(name)
  }
}

8、接口interface

当一个对象类型多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的。作用:给对象约束属性和方法

typescript中,我们定义对象的方式要用关键字interface(接口),我的理解是使用interface来定义一种约束,让数据的结构满足约束的格式。定义方式如下:

// 基础用法:
interface IPerson {
  name: string
  age: number
  sayHi: () => void
}

const p1: IPerson = {
  name: '苍老师',
  age: 18,
  sayHi() {
    console.log('i like')
  }
}
  • 使用interface关键字声明接口
  • 接口名称可以是任何合法的变量名称,推荐以I开头
  • 声明接口后,直接使用接口名称作为变量的类型
  • 因为每一行只有一个属性类型,因此,属性类型后没有;(分号)
//这样写是会报错的 因为我们在person定义了a,b但是对象里面缺少b属性
//使用接口约束的时候不能多一个属性也不能少一个属性
//必须与接口保持一致
interface Person {
	b: string,
	a: string
}

const person:Person = {
	a: '213'
}


//重名interface  可以合并
interface A{name:string}
interface A{age:number}
var x:A={name:'xx', age:20}

//继承
interface A{
    name:string
}
 
interface B extends A{
    age:number
}
 
let obj:B = {
    age:18,
    name:"string"
}

interface VS type

总结:
interface 和 type 的区别,接口interface 只能约束对象,而类型别名 type 不仅可以为对象指定类型,实际上可以为任何类型指定别名。
推荐:能使用type就使用type,更灵活,更简单

接口继承

如果两个接口之间有相同的属性和方法,可以将公共的属性和方法抽离出来,通过继承来实现复用

// 比如,这两个接口都有x、y两个属性,重复写两次,可以,但是很繁琐
interface Point2D {
  x: number
  y: number
}

interface Point3D {
  x: number
  y: number
  z: number
}

// 更好的方式
interface Point2D {
  x: number
  y: number
}

// 继承Point2D
interface Point3D extends Point2D {
  z: number
}

type 如何和 interface 一样实现继承的效果?

type Person = {
  usename: string
  age: number
  sayHi: () => void
}

//  & 与连接符:既要满足前面的也要满足后面的
//  | 或连接符:满足其中一个即可
type Student = {
  score: number
  sleep: () => void
} & Person

接口继承:可以实现让一个接口使用另一个接口的类型约束,实现接口的复用。

9、元组Tuple

限定数组的固定类型和数据长度,元组类型是另一种类型的数组,他确切地知道包含多少个元素,以及特定索引对应的类型

// 地图经纬度
let arr6: [number, number] = [13.334, 36.2323]

// 或者:
let arr6: [number, number];
arr6 = [13.334, 36.2323]

10、字面量

字面量类型就是把字面量当做类型来用。eg:

let s1 = 'hello TS';
const s2 = 'hello TS';

通过TS类型推论机制,可以得到:s1的类型时string,s2的类型为 ‘hello TS’;
解释:s2是一个常量,它的值不能变化只能是 ‘hello TS’,所以,它的类型为 ‘hello TS’。此次 ‘hello TS’,就是一个字面量类型,也就是说某个特定的字符串也可以作为TS中的类型

  • 使用模式:字面量类型配合联合类型一起使用
  • 使用场景:用来表示一组明确的可选值列表

比如:在贪吃蛇游戏中,游戏的方向的可选值只能是上、下、左、右中的任意一个

11、枚举类型enum

枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值;
枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个
如果不设置值,默认从 0 开始

// 创建枚举
enum Direction {
  Up,
  Dowm,
  Left,
  Right
}

// 使用枚举类型
function changeDirection(direction: Direction) {
  console.log(direction)
}
  • 使用enum关键字定义枚举
  • 约定枚举名称以大写字母开头
  • 枚举中多个值之间通过,(逗号)分割
  • 定义好枚举后,直接使用枚举名称作为类型注释

数字枚举

我们把枚举成员的值为数字的枚举,称为数字枚举。注意:枚举成员是有值的,默认为:从0开始自增的数值

// 给枚举中的成员初始化值
enum Direction {
  Up = 10,
  Dowm,
  Left,
  Right
}
//  Dowm => 11, Left => 12, Right => 13

字符串枚举

枚举成员的值为字符串。字符串枚举没有自增长的行为,因此,字符串枚举的每个成员必须有初始值。

enum Direction {
  Up = 'Up',
  Dowm = 'Dowm',
  Left = 'Left',
  Right = 'Right'
}

枚举实现原理

枚举是TS为数不多的非JavaScript类型级扩展的特性之一
其他类型仅仅被当做类型,而枚举不仅用作类型,还提供值(枚举成员都是有值的)
也就是说,其他的类型会在编译为JavaScript代码时自动移除,但是 枚举类型会被编译为JS 代码

// 会被编译为以下代码:
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Dowm"] = 1] = "Dowm";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));

说明:枚举与前面安静到的字面量类型+联合类型组合的功能类似,都用来表示一组明确的可选值列表。
一般情况下,推荐字面量类型 + 联合类型组合的方式,因为相比枚举,这种方式更加直观、简洁、高效。

12、any类型

any类型:原则上不推荐使用,只有在迫不得已的时候才可以用一下,否则会变成 AnyScript,失去 TS 类型保护机制

let obj2: any = {x: 0}

const n: number = obj2;

尽可能的避免使用any类型,除非临时使用any来避免书写很长、很复杂的类型,其他隐式具有any类型的情况

  • 声明变量不提供类型也不提供默认值
  • 函数参数不加类型

注意:因为不推荐使用any,所以,这两种情况下都应该提供类型

13、类型断言 | 交叉类型

类型断言:强行指定获取到的结果类型
有时候你会比TS更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型,eg:
const aLink = document.getElementById('link')
注意:该方法返回值的类型是HTMLElement,该类型只包含所有标签公共的属性和方法,不包含a标签特有的href等属性,因此,这个类型太宽泛,无法操作href等a标签特有的属性和方法
解决办法:这种情况下就需要使用类型断言指定更加具体的类型。

const aLink = document.getElementById('link') as HTMLAnchorElement
// aLink.href, 敲代码段的时候直接有提示
  • 语法:使用as关键字实现类型断言
  • 关键字as后面的类型时一个更加具体的类型 (HTMLAnchorElement)

另一种写法,使用<> 语法,不常用,知道即可
const alink2 = <HTMLAnchorElement>document.getElementById('link')

总结:当函数获取到的结果类型较为宽泛时,我们有知道具体类型,就可以使用断言强行指定类型。

交叉类型

// 交叉类型: 多种类型的集合,联合对象将具有所联合类型的所有成员
interface People {
  age: number,
  height: number
}
interface Man{
  sex: string
}
const xiaoman = (man: People & Man) => {
  console.log(man.age)
  console.log(man.height)
  console.log(man.sex)
}
xiaoman({age: 18,height: 180,sex: 'male'});

14、泛型

泛型是可以保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class中。
泛型在保证类型安全(不丢失类型信息的同时,可以让函数等与多种不同的类型一起工作,灵活可复用。)

// 定义一个getId,传入一个值,返回这个值,可以传入任意类型
// any 可以解决问题,但也会带来问题:返回值也是any没有提示了
function getId(val: number | string | boolean) {
	return val
}

解决方案:

// 期望:调用getId函数时来指定传入参数的类型
// <T> 在声明泛型
// (val: T) 使用泛型
// 调用函数时传入泛型指定的具体类型
function getId<T>(val: T) {
  return val
}

console.log(getId<number>(123))
console.log(getId<string>('123'))

// 简化泛型函数调用,  也可以使用,调用时不用加<>
console.log(getId(123))

上面的T和下面的Type是一样的,知识变量名称不同罢了

泛型约束

// 如下代码:当传输string,正确,当输入number时,报错,所以需要对val进行约束,即:泛型约束
function getId<T>(val: T) {
  console.log(val.length)
  return val
}

添加约束(常见方法)

// 定义接口
interface ILength {
  length: number
}

// 使用,添加约束,给泛型找爸爸
function getId<T extends ILength>(val: T) {
  console.log(val.length) // 输入val代码提示length属性
  return val
}
  • 通过extends关键字使用该接口,为泛型添加约束
  • 该约束表示:传入的类型必须具有length属性

多个类型变量

泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)

// 需求,定义一个函数,传入一个对象,在传入一个字符串属性名,返回属性值
function getProp(obj: object, key: string) {
	return obj[key] // 这种方式会报错,不知道是否这个key
}
function getProp<O, K extends keyof O>(obj: O, key: K) {
	return obj[key]
}

let person = {name: 'jack', age: 18}
getProp(person, 'name')
  • 添加第二个类型变量key,两个类型变量之间使用 ,逗号分隔
  • keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
  • 本示例中 keyof O 实际上获取的是person对象所有键的联合类型,也就是:‘name’ | ‘age’
  • 类型变量key受O约束,可以理解为:key只能是O所有键中的任意一个,或者说只能访问对象中存在的属性

泛型接口

interface Student {
	id: number
	name: string
	hobby: string[]
}
let s1: Student = {
	id: 123,
	name: '波多小姐',
	hobby:['瑜伽', '运动', '歌唱']
}

// 转化
interface Student<T> {
	id: number
	name: T
	hobby: T[]
}

let s1: Student<string> = {
	id: 123,
	name: '波多小姐',
	hobby:['瑜伽', '运动', '歌唱']
}

参考:
视频链接
小满视频讲解
type和interface的区别

知识总结参考

TypeScript汇总

相关的面试题

interface和type的区别

两者最大的区别在于,interface只能用于定义对象类型,而 type 的声明方式除了对象之外还可以定义交叉、联合、原始类型等,类型声明的方式适用范围显然更加广泛。