TypeScript学习
写在前面:
本博客主要参考codewhy老师的课程,和下面视频+对应笔记文档,以及自己的总结和补充
🪩 禹神:三小时快速上手TypeScript,TS速通教程_哔哩哔哩_bilibili
认识TypeScript
TypeScript简介
TypeScript 由微软开发,是基于 JavaScript 的⼀个扩展语⾔。
TypeScript 包含了 JavaScript 的所有内容,TypeScript 是 JavaScript 的超集。
TypeScript 增加了:静态类型检查、接⼝、 泛型等很多现代开发特性,更适合⼤型项⽬ 的开发。
TypeScript 需要编译为 JavaScript ,然后交给浏览器或其他 JavaScript 运⾏环 境执⾏
为什么用TypeScript
typescript可以在开发阶段进行类型校验
- js现状
JavaScript的定位是浏览器脚本语⾔,⽤于在⽹⻚中嵌⼊简单的逻辑,且代码量少。但js越来越流⾏,已经可以全栈编程,写大型项目会出现很多困扰 - js困扰
以下问题放在.js文件夹不会飘红报错,只有运行后才会控制台2报错,但是放到.ts文件夹会飘红报错(不用执行,静态类型检查)
不清楚的数据类型
1
2let welcome = 'hello'
welcome() // 此⾏报错:TypeError: welcome is not a function有漏洞的逻辑
1
2
3
4
5const str = Date.now() % 2 ? '奇数' : '偶数'
if (str !== '奇数'){
alert('hello')
}else if(str === '偶数'){
alert('world') }访问不存在的属性
1
2const obj = { width: 10, height: 15 };
const area = obj.width * obj.heigth;低级的拼写错误
1
const message = 'hello,world' message.toUperCase() //遗漏了一个"p"
- ts是静态类型检查
不用运行,不对的写完就知道不对了
开始麻烦,但是后面省心同样的功能,TypeScript 的代码量要⼤于 JavaScript,但由于 TypeScript 的代码结构更加 清晰,在后期代码的维护中 TypeScript 却胜于 JavaScript
编译 TypeScript(两种方式)
浏览器不能直接运行tx,需要编译为ts再交给编译器执行
如果使用脚手架,那么它会基于脚手架webpack,vite自动编译ts,不用下面的操作
命令⾏编译(几乎不用,了解)
要把 .ts ⽂件编译为 .js ⽂件,需要配置 TypeScript 的编译环境,步骤如下:
- 创建⼀个 demo.ts ⽂件,例如:
1
2
3
4
5const person = {
name: '虞寻歌',
home: '载酒'
}
console.log(`${person.name},来自${person.home}`) - 全局安装
TypeScript npm i typescript -g
创建完成后,全局有一个tsc指令用于编译,再根据这个命令把ts文件变成js文件
- 第三步:使⽤命令编译 .ts ⽂件
tsc demo.ts
- “注意:现在安装最新的 TSC 编译器并不会新增全局命令,需要写成 npx tsc index 才可以!”
- 结果展示
成功后会多出一个对应名字的.js文件,同时原来.ts中的汉字会被编码在任何js环境都能识别的样式,防止乱码
每次更新.ts后都要重新编译生成.js
⾃动化编译(重要)
成功后,.ts文件修改保存后,对应的.js文件自动更新修改,同时不会把错误内容同步到.js文件中
- 创建 TypeScript 编译控制⽂件:
tsc --init
在新生成的tsconfig.json文件中的1
2"target": "es2016",
//表示使用ES7;es2015表示ES6,改成ES2015或者ES6 - 监视⽬录中的 .ts ⽂件变化
tsc --watch 或 tsc -w
如果要监视一个文件,那么在后面加文件名,如’ tsc –watch demo.ts’,如果想要监视当前文件夹的所有ts文件,那什么都不加
此时生成.js文件
同时因为刚才配置了使用ES6,声明person不再转换成var而是使用了const,
上面"use strict";
表示使用严格模式 - 配置:当编译出错时不⽣成 .js ⽂件,修改正确后再生成
(不推荐但是有的做法:)tsc --noEmitOnError --watch
要这样做:修改tsconfig.json 中的配置,把"esModuleInterop": true,
这行的注释解开,如图
TypeScript类型
类型声明
使⽤
:
来对变量或函数形参,进⾏类型声明限制
函数参数多传或者少传都是不可以的
vscode右键-格式化文档,自动帮你在:后面留出空格
注意区分大小写,string不能写成String!!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22let a: string // 变量a以后只能存储 字符串类型
let b: number // 变量b只能存储数值
let c: boolean // 变量c只能存储布尔值
a = 'hello'
a = 100 // 警告: 不能将类型“number”分配给类型“string”
b = 666
b = '你好' // 警告: 不能将类型"string"分配给类型"number"
c = true
c = 666 // 警告: 不能将类型“number”分配给类型“boolean”
// 函数:对形参和返回值进行限制,参数x必须是数字,参数y也必须是数字,函数返回值也必须是数字
function demo(x: number, y: number): number {
return x + y
}
demo(100, 200)
demo(100, '200') // 警告: 类型“string"的参数不能赋给类型"number"的参数
demo(100, 200, 300) // 警告: 应有 2个参数, 但获得 3个
demo(100) // 警告: 应有 2个参数,但获得 1个在
:
后也可以写字⾯量类型,不过实际开发中几乎不用
1 | let a: '你好' //a的值只能为字符串"你好" |
常用写法
1 | let name: string ="虞寻歌" |
- 类型推断(可以但不要做)
如let b: 100
不声明类型也不会报错(可以理解为这是ts善良的部分但不要利用这个善良)
类型总览
JavaScript 中的数据类型
① string ② number ③ boolean ④ null ⑤ undefined ⑥ bigint ⑦ symbol ⑧ object
备注:其中 object 包含: Array 、 Function 、 Date 、 Error 等……
TypeScript 中的数据类型
- 上述所有 JavaScript 类型
再ts中的undefined和null它们既是实际的值,也是自己的类型1
2let n: null = null
let u: undefined = undefined - 六个新类型: ① any ② unknown ③ never ④ void ⑤ tuple ⑥ enum
- 两个用于自定义类型的方式: ① type ② interface
注意点
前面提到的,不能把string写成String(类似java中int和 integer)
在 JavaScript 中的这些内置构造函数: Number、String、Boolean,⽤于创建对应的包装对象, 在⽇常开发时==很少使⽤==,在 TypeScript 中也是同理,所以 在 TypeScript 中进⾏类型声明时,通常都是⽤⼩写的 number 、 string 、 boolean
例如下⾯代码:
1 | let str1: string //TS官方推荐的写法 |
TS中数组类型写法
在开发中,数组一般存放相同的类型,不要存放不同的类型
1 | let names: string[] = ["虞寻歌","图蓝","B8017913"] |
TS中的Object类型
Object类型中的属性不要再指定类型了,让编译器自己推导类型即可
1 | let info = { |
原始类型VS包装对象-理解
- 原始类型 VS 包装对象
- 原始类型:如 number 、 string 、 boolean ,在 JavaScript 中是简单数据 类型,它们在内存中占⽤空间少,处理速度快。
- 包装对象:如 Number 对象、 String 对象、 Boolean 对象,是复杂类型,在 内存中占⽤更多空间,在⽇常开发时很少由开发⼈员⾃⼰创建包装对象。
- ⾃动装箱:
JavaScript 在必要时会⾃动将原始类型包装成对象,以便调⽤⽅法或访问属性
1 | // 原始类型字符串 |
常⽤类型声明与语法(重要)
声明对象类型
⼀般对象
在熟悉后面加?
代表这个属性可以有也可以没有,如果没加的话必须有,否则报错
属性之间可以用,
和;
或者换行
分隔开1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 限制person1对象必须有name属性,age为可选属性
let person1: { name: string, age?: number }
// ,也能⽤分号做分隔
let person2: { name: string; age?: number }
// 含义同上,也能⽤换⾏做分隔
let person3: {
name: string
age?: number // 加?代表可以为空
}
// 如下赋值均可以
person1 = {name:'李四',age:18}
person2 = {name:'张三'}
person3 = {name:'王五'}
// 如下赋值不合法,因为person3的类型限制中,没有gender属性的说明
person3 = {name:'王五',gender:'男'}索引签名:
[key: string]: any
允许定义对象可以具有任意数量的属性,这些属性的键和类型是可变的, 常⽤于:描述类型不确定的属性,(具有动态属性的对象)。
1 | // 限制person对象必须有name属性,可选age属性但值必须是数字,同时可以有任意数量、任意类型的其他属性 |
声明函数类型
指定参数类型,返回值类型可以指定,也可以推导Typescript对于传入的函数类型的参数个数不进行检测
(函数类型知识还有好多,远不止下面这一点,我没做笔记)
1 | function sum(num1:number, num2:number): number { return num1 + num2 } |
1 | function parseLyric(lyric: string){ |
1 | let count: (a: number, b: number) => number |
注意:
- TypeScript 中的 => 在函数类型声明时表示==函数类型,==描述其==参数类型==和返回类型。
- JavaScript 中的 => 是⼀种定义函数的语法,是具体的函数实现。
- 函数类型声明还可以使⽤:接⼝、⾃定义类型等⽅式,下⽂中会详细讲解。
函数类型表达式
格式:(参数列表) => 返回值类型
1 | typr Func1 = (a: number, b: number) => number |
调用签名
如果想描述一个带有属性的函数,可以在一个对象类型中写一个调用签名
注意这个语法跟函数类型表达式稍有不同,在参数列表和返回的类型之间用的是:而不是=>
开发中如何选择:
1.如果只是描述函数类型本身(函数可以被调用),使用函数类型表达式
2.如果在描述函数作为对象可以被调用,同时也有其他属性时,使用函数调用签名
1 | //函数类型表达式 |
声明数组类型
1 | let arr1: string[] |
tuple
元组Tuple是⼀种==特殊的数组类型==,可以存储固定数量的元素,并且每个元素的类型是==已知==的且可以==不同==。元组⽤于精确描述⼀组值的类型, ? 表示可选元素。
1 | // 第⼀个元素必须是 string 类型,第⼆个元素必须是 number 类型。 |
其他类型与语法
any
无法确定一个变量的类型,并且可能它会发生一些变化,这个时候可以使用any类型
(实在写不下去再用any,不然用ts干啥呢)
- any含义
any的含义是任意类型,⼀旦将变量类型限制为 any ,那就意味着放弃了对该变量的类型检查
1 | // 明确的表示a的类型是 any —— 【显式的any |
- any会传染!!
注意点: any 类型的变量,可以赋值给任意类型的变量(容易自己一把火给别人也烧了)
1 | /* 注意点:any类型的变量,可以赋值给任意类型的变量 */ |
unknown
unknown 的含义是: 未知类型
适⽤于起初不确定数据的具体类型,要后期才能确定
unknown 可以理解为⼀个类型安全的any
1
2
3
4
5
6
7
8
9// 设置a的类型为unknown
let a: unknown
//以下对a的赋值,均符合规范
a = 100
a = false
a = '你好'
// 设置x的数据类型为string
let x: string
x = a //警告:不能将类型"unknown"分配给类型"string"unknown的代码安全性
unknown会强制开发者在使⽤之前进⾏类型检查,从⽽提供更强的类型安全性
看以下代码处理方式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 设置a的类型为unknown
let a: unknown
a = 'hello'
//第⼀种⽅式:加类型判断
if(typeof a === 'string'){
x = a
console.log(x)
}
//第⼆种⽅式:加断⾔
x = a as string
//第三种⽅式:加断⾔
x = <string>aany和unknown属性读取
读取any类型数据的任何属性都不会报错,⽽ unknown 正好与之相反
1 | let str1: string |
never
never 的含义是:任何值都不是,即: 不能有值,例如 undefined 、 null 、 ‘’ 、 0 都不⾏!
项目中很少遇到never(出现也是1或3)
never可⽤于限制函数的返回值
返回“从不”的函数不能具有可访问的终结点1
2
3
4// 限制throwError函数不需要有任何返回值,任何值都不⾏,像undeifned、null都不⾏
function throwError(str: string): never {
throw new Error('程序异常退出:' + str)
}⼏乎不⽤ never 去直接限制变量,因为没有意义,
例如:1
2
3
4
5
6
7
8/* 指定a的类型为never, 那就意味着a以后不能存任何的数据了 */
let a: never
// 以下对a的所有赋值都会有警告
a = 1
a = true
a = undefined
a = nullnever 一般是TypeScript主动推断出来的
1
2
3
4
5
6
7
8
9// 指定a的类型为string
let a: string // 给a设置⼀个值
a = 'hello'
if (typeof a === 'string'){
console.log(a.toUpperCase())
}else {
console.log(a) // TypeScript会推断出此处的a是never,因为没有任何⼀个值符合此处的逻辑
}
void
void 的含义是空,即函数不返回任何值,可以省略
- void通常⽤于函数返回值声明
1
2
3
4function logMessage(msg:string):void{
console.log(msg)
}
logMessage('你好')
注意:编码者没有编写 return 指定函数返回值,所以 logMessage 函数是没有==显式返回值==的,但会有⼀个==隐式返回值,是 undefined== ,虽然函数返回类型为 void ,但也是可以接受 undefined 的,简单记: undefined是void可以接受的⼀种”空”
- void符合规范的写法
1 | // 无警告 |
- 深入理解:
实际开发中真的不希望别人去关注函数的返回值,我的函数也不会返回东西,那么就用void
undefined和void
那限制函数返回值时,是不是 undefined 和 void 就没区别呢?—— 有区别。因为还有 这句话 :【返回值类型为 void 的函数,调⽤者不应依赖其返回值进⾏任何操作!】对⽐下 ⾯两段代码:
1 | function logMessage(msg: string): void { |
1 | function logMessage(msg: string): undefined { |
- 理解 void 与 undefined
- void 是⼀个⼴泛的概念,⽤来表达”空”,⽽ undefined 则是这种”空”的具体 实现。
- 因此可以说 undefined 是 void 能接受的⼀种”空”的状态。
- 也可以理解为: void 包含 undefined ,但 void 所表达的语义超越了 undefi ned , void 是⼀种意图上的约定,⽽不仅仅是特定值的限制。
- 总结
如果⼀个函数返回类型为 void ,那么:
- 从语法上讲:函数是可以返回 undefined 的,⾄于显式返回,还是隐式返回,这⽆ 所谓!
- 从语义上讲:函数调⽤者不应关⼼函数返回的值,也不应依赖返回值进⾏任何操作! 即使我们知道它返回了 undefined
⼀个特殊情况void
观察如下两段代码:
代码段1(正常)
在函数定义时,限制函数返回值为 void ,那么函数的返回值就必须是空。
1 | function demo():void{ |
代码段2(特殊)
使⽤ 限制函数返回值为 void 时, TypeScript 并不会严格要求函数返回空。
1 | type LogFunc = () => void // LogFunc的类型是一个函数,该函数不接受任何参数,并且其返回值是void类型的 |
另一种写法:
1 | let PrintData: () => void; |
为什么会这样?
是为了确保如下代码成⽴,我们知道 Array.prototype.push 的返回值是⼀个数字, ⽽ Array.prototype.forEach ⽅法期望其回调的返回类型是 void 。
1 | const src = [1, 2, 3]; |
官⽅⽂档的说明:Assignability of Functions
object(少用到)
object与Objec,实际开发中⽤的较少,因为范围太⼤了
object
object的含义是所有⾮原始类型,可存储:对象、函数、数组等,由于限制 的范围⽐较宽泛,在实际开发中使⽤的相对较少。
1 | let a:object //a的值可以是任何【⾮原始类型】,包括:对象、函数、数组等 |
Object
- 官⽅描述:所有可以调⽤Object⽅法的类型。
- 简单记忆:除了 undefined 和 null 的任何值。
- 由于限制的范围实在太⼤了!所以实际开发中使⽤频率极低。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19let b:Object
//b的值必须是Object的实例对象(除去undefined和null的任何值)
// b能存储的类型是可以调用到Object方法的类型
// 以下代码,均⽆警告,因为给a赋的值,都是Object的实例对象
b = {}
b = {name:'张三'}
b = [1,3,5,7,9]
b = function(){}
b = new String('123'
class Person {}
b = new Person()
b = 1 // 1不是Object的实例对象,但其包装对象是Object的实例
b = true // truue不是Object的实例对象,但其包装对象是Object的实例
b = '你好' // "你好"不是Object的实例对象,但其包装对象是Object的实例
// 以下代码均有警告
b = null // 警告:不能将类型"null"分配给类型"Object"
b = undefined // 警告:不能将类型"undefined"分配给类型"Object"
enum
枚举enum可以定义一组命名常量,它能增强代码的可读性,也让代码更好维护
实际开发中很可能不小心写错单词,为了避免出错,把一组一组的相关的值放在枚举中(比如上下左右,东南西北)
数字枚举
数字枚举是最常⻅的枚举类型,其成员的值会⾃动递增,且数字枚举还具备反向映射的特点,在下⾯代码的打印中,发现可以通过值来获取对应的枚举成员名称
1 | // 定义一个描述【上下左右】方向的枚举Direction |
也可以指定枚举成员的初始值,其后的成员值会⾃动递增。
1 | enum Direction { |
使⽤数字枚举完成walk 函数中的逻辑,此时我们发现: 代码更加直观易读,⽽且类型安全,同时也更易于维护function walk(n: **Direction**)
1 | enum Direction { |
字符串枚举
枚举成员的值是字符串没有反向映射
1 | enum Direction { |
常量枚举
官⽅描述:常量枚举是⼀种特殊枚举类型,它使⽤ const 关键字定义,在编译时会被内联,避免⽣成⼀些额外的代码
何为编译时内联?
所谓”内联”其实就是 TypeScript 在编译时,会将枚举成员引⽤替换为它们的实际值, ⽽不是⽣成额外的枚举对象。这可以减少⽣成的 JavaScript 代码量,并提⾼运⾏时性能。
使⽤普通枚举的 TypeScript 代码如下:
1 | enum Directions { |
编译后⽣成的 JavaScript 代码量较⼤ :
1 | ; |
使⽤常量枚举的 TypeScript 代码如下:
1 | const enum|Directions { |
编译后⽣成的 JavaScript 代码量较⼩:
1 | ; |
type(重要)
type 可以为任意类型创建别名,让代码更简洁、可读性更强,同时能更⽅便地进⾏类型复⽤和扩展
类型别名
type关键字定义类型别名,跟类型名称,例如下⾯代码中num是number类型别名。
1 | type num = number; |
再比如给对象类型起一个别名
1 | type Person = { |
再比如
1 | type ID=number | string; |
联合类型(或)
TypeScript的类型系统允许我们使用多种运算符,从现有类型中构建新类型
由两个或多个其他类型组成的类型
联合了偶像使用时候要小心,要进行类型缩小,比如用if进行类型判断,推断出具体类型再进行操作
1 | type Status = number | string; |
交叉类型(并)
交叉类型允许将多个类型合并为⼀个类型
合并后的类型将拥有所有被合并类型的成员,交叉类型通常⽤于对象类型
1 | //⾯积 |
TS中对象类型的声明(type和interface)
- 用type声明对象
比如给对象类型起一个别名1
2
3
4
5
6
7
8type Person1 = {
name: string;
age: number;
}
function printPerson(person: Person1) {
console.log(person.name);
}
printPerson({name: '西西', age: 23}) - 用interface声明对象
注意语法,和type不一样1
interface Person2 {}
- interface和type的区别
总之,如果声明对象建议用interface,非对象类型的定义建议用type
大部分情况二者可以随便用,但是:
- type类型使用范围更广,接口类型只能用来声明对象
- 声明对象时,type中的在interface中都适用interface可以多次声明同一个接口名称
- interface可以被类实现
- interface支持继承
1
2
3
4
5
6
7interface animal {
name: string;
speak() {}
}
interface dog extends animal {
bark() {}
}
其他语法细节
类型断言as
有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言
比如通过 document.getElementByld,TypeScript只知道该函数会返回 HTMLElement,但并不知道它具体的类型
理解:比如一个类,你知道它是什么类型的,那么这时候使用类型断言as
告诉编译器这个类的类型,比类型缩小更好用
比如下面的例子,确定这个类是图片元素加as HTMLImageElement
,这样可以直接.src和.alt,否则会报错
这也是类型断言最常用的用法
1 | const imgEl = document.querySelector('.img') as HTMLImageElement; |
类型断言规则
- 断言规则
TypeScript只允许类型断言转换为更具体或者**不太具体(any或unknown)**的类型版本,此规则可防止不可能的强制转换
简而言之,可以断言成具体类型,或any和unknown,其他的不能转来转去
非空类型断言!
非空断言使用!
,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测,使用时一定要慎重,确定它一定有值再用
理解:
比如说,你的对象中有一个属性是可能存在的,你想对这个不确定是否存在的属性进行操作,那么编译器会报错
如果只是想访问不确定是否存在的属性,可以用可选链?.
如果要进行赋值等操作,可以使用非空类型断言,强制告诉编译器这个属性一定存在
(也可以用类型缩小)
看下面代码就懂了
1 | //定义接口 |
字面量类型使用规则
字面量类型是指一个具体的值,比如字符串、数字、布尔值、null、undefined等
字面量类型可以作为类型别名使用
也可以在封装请求方法中使用(仔细看下面代码,注意细节)
强制变成字面量类型as const
(字面量推理)
1 | type Gender = '男' | '女'; |
类型缩小(类型保护)
这个很重要,前面已经提过很多次了
简而言之,就是在给定的执行路径中(比如if语句),缩小比声明时更小的类型
常见的类型保护有如下几种:
typeof;平等缩小(比如===、!==)instanceof;in
- typeof(使用做多)
1
2
3
4
5
6
7function printID(id: string | number){
if(typeof id === 'string'){
console.log(id.length);
}else{
console.log(id);
}
} - 平等缩小(一般判断字面量类型)
1
2
3
4
5
6
7//方向的类型判断
type Direction ="left"|"right"|"up"| "down"
function switchDirection(direction:Direction){
if(direction === "left"){
console.log("左边哦")
}
} - instanceof
1
2
3
4
5
6
7
8//传入一个日期,打印日期
function printDate(date:string | Date){
if(date instanceof Date){
console.log(date.getTime())
} else {
console.log(date)
}
} - in操作符
主要判断某一个对象里是有某个属性
如果指定的属性在指定的对象或其原型链中,则in 运算符返回true;1
2
3
4
5
6
7type Fish ={swim:() => void}
type Dog ={run:() =>void}
function move(animal: Fish Dog){
if('swim'in animal){
animal.swim()
}else {animal.run()}
}
复习类相关知识
类 class
1 | class Person { |
Student 继承 Person
1 | class Student extends Person { |
属性修饰符
修饰符 | 含义 | 具体规则 |
---|---|---|
public | 公开的 | 可以被:类内部、子类、类外部访问。 |
protected | 受保护的 | 可以被: 类内部、子类访问。 |
private | 私有的 | 可以被: 类内部访问。 |
readonly | 只读属性 | 属性无法修改。 |
public 修饰符
Person 类
1 | class Person { |
Student 继承 Person
1 | class Student extends Person { |
属性的简写形式
完整写法
1 | class Person { |
protected 修饰符
Person类
1 | class Person { |
Student 继承 Person
1 | TypeScript |
private 修饰符
1 | class Person { |
readonly 修饰符
1 | class Car { |
抽象类
- 概述:抽象类是⼀种⽆法被实例化的类,专⻔⽤来定义类的结构和⾏为,类中可以写抽象 ⽅法,也可以写具体实现。抽象类主要⽤来为其派⽣类提供⼀个基础结构,要求其派⽣类 必须实现其中的抽象⽅法。
- 简记:抽象类==不能实例化==,其意义是==可以被继承==,抽象类⾥可以有==普通⽅法==、也可以有==抽象⽅法==。
通过以下场景,理解抽象类:
我们定义⼀个抽象类 Package ,表示所有包裹的基本结构,任何包裹都有重量属性 weigh t ,包裹都需要计算运费。但不同类型的包裹(如:标准速度、特快专递)都有不同的运费计算 ⽅式,因此⽤于计算运费的 calculate ⽅法是⼀个抽象⽅法,必须由具体的⼦类来实现。
Package 类 TypeScript
1 | abstract class Package { |
StandardPackage 类继承了 Package ,实现了 calculate ⽅法:
1 | // 标准包裹 |
ExpressPackage 类继承了 Package ,实现了 calculate ⽅法:
ExpressPackage 类(特快包裹)
1 | class ExpressPackage extends Package { |
[!NOTE] 总结:何时使⽤抽象类?
1.定义通用接口 :为⼀组相关的类定义通⽤的⾏为(⽅法或属性)时。
2.提供基础实现:在抽象类中提供某些⽅法或为其提供基础实现,这样派⽣类就可以继承这 些实现。
3.确保关键实现:强制派⽣类实现⼀些关键⾏为。
4.共享代码和逻辑:当多个类需要共享部分代码时,抽象类可以避免代码重复。
interface(接⼝)
interface 是⼀种定义结构
的⽅式,主要作⽤是为:类、对象、函数等规定⼀种契约
,这样 可以确保代码的⼀致性和类型安全,但要注意 interface 只能
定义格式
,不能
包含任何实现
!
定义类结构
1 | // PersonInterface接口,用与限制Person类的格式 |
定义对象结构
1 | interface UserInterface { |
定义函数结构
1 | // 定义函数结构 |
接口之间的继承
一个 interface 继承另一个 interface,从而实现代码的复用
1 | interface PersonInterface { |
接口自动合井(可重复定义)
1 | // PersonInterface接口 |
总结:何时使⽤接⼝?
- 定义对象的格式: 描述数据模型、API 响应格式、配置对象……..等等,是开发中⽤的最多 的场景。
- 类的契约:规定⼀个类需要实现哪些属性和⽅法。
- 扩展已有接⼝:⼀般⽤于扩展第三⽅库的类型, 这种特性在⼤型项⽬中可能会⽤到。
⼀些相似概念的区别
interface 与 type 的区别
(这部分在上文type–TS中对象类型的声明里也有)
相同点: interface 和 type 都可以⽤于定义对象结构,在定义对象结构时两者可以 互换。
不同点:
interface :更专注于定义对象和类的结构,⽀持继承、合并。
type :可以定义类型别名、联合类型、交叉类型,但不⽀持继承和⾃动合并。
interface 和 type 都可以定义对象结构 TypeScript
1 | // 使⽤ interface 定义 Person 对象 |
interface 可以继承、合并
1 | interface PersonInterface { |
type 的交叉类型
1 | // 使⽤ type 定义 Person 类型,并通过交叉类型实现属性的合并 |
interface 与 抽象类的区别
相同点:都能定义⼀个类的格式(定义类应遵循的契约)
不相同:
- 接⼝:只能描述结构,不能有任何实现代码,⼀个类可以实现多个接⼝。
- 抽象类:既可以包含抽象⽅法,也可以包含具体⽅法, ⼀个类只能继承⼀个抽象类。
⼀个类可以实现多个接⼝ TypeScript
1 | // FlyInterface 接⼝ |
泛型和类型声明文件
泛型
泛型允许我们在定义函数、类或接⼝时,使⽤类型参数来表示未指定的类型,这些参数在具体使⽤时,才被指定具体的类型,泛型能让同⼀段代码适⽤于多种类型,同时仍然保持类型的安全性。
举例:如下代码中 <T>
就是泛型,(不⼀定⾮叫 T ),设置泛型后即可在函数中使⽤ T 来表 示该类型:
1 | // 泛型函数 |
1 | // 泛型约束 |
类型声明文件
类型声明⽂件是 TypeScript 中的⼀种特殊⽂件,通常以 .d.ts 作为扩展名。它的主要作⽤是为现有的
JavaScript 代码提供类型信息,使得 TypeScript 能够在使用这些 JavaScript 库或模块时进行类型检查和提示。
1 | // demo.js |
typescript高频基础面试题
TypeScript 和 JavaScript 的区别是什么
TypeScript 是 JavaScript 的超集,添加了静态类型检查(能够在编译时检查类型错误)
类型注解:TS 支持 : string、: number 等类型注解,JS 无此功能
接口(Interface):TS 可定义对象结构(如 interface Person { name: string; age: number })。
工具支持:TS 提供更好的 IDE 支持(如自动补全、错误提示)
1 | // JS:无类型检查 |
1 | // 显式类型注解 |
什么是联合类型(Union Types)?如何使用
合类型:用 | 表示参数或变量可以是多种类型中的一种。
应用场景:处理可能为多种类型的值(如输入框内容可能是数字或字符串)
1 | function printId(id: number | string): void { |
可空类型怎么处理
- 可空类型就是用
| null | undefined
表示变量可能为空.
比如let name: string | null
- 处理的时候有两种方式:
- 用可选链 ?.:比如 user?.name,如果 user 是 null 或 undefined,就不会报错直接返回 undefined。
- 用空值合并 ??:比如 text ?? “默认值”,如果 text 是 null/undefined,就用右边的默认值。
- 实际场景:在处理 API 数据时,接口返回的字段可能为空,用 ?? 给默认值能避免页面渲染报错。
any 和 unknown 有什么区别
这两个都是“不确定类型”,但 any 更危险,因为它绕过了类型检查