一、TS的基础概念
1.什么是TS
它是JS的一个超集,在原有的语法基础上,添加强类型并切换为基于类的面向对象语言。
TS属于强类型语言,JS属于弱类型语言,强类型语言支持静态语言和动态语言,弱类型语言支持动态语言。
TypeScript | JavaScript | |
---|---|---|
类型检测 | 强类型语言,支持动态语言和静态语言 | 弱类型语言,无静态类型选项 |
面向项目 | 面向解决大型的复杂项目、架构、代码维护复杂场景 | 脚本语言, 脚本化语言,用于面向简单的页面场景 |
自主检测 | 可以在编译期间自主发现并纠正错误 | 运行时,执行报错 |
运行流程 | 先被浏览器编译成js语言 | 在浏览器可以直接使用 |
复杂性 | 支持模块化、接口、泛型 | - |
强类型语言和弱类型语言
强类型语言:强类型语言不允许改变变量的数据类型,除非进行强制类型转换。
弱类型语言:定义与强类型语言相反,一个变量可以被赋予不同数据类型的值。PHP、Ruby、Python。
动态语言和静态语言
静态语言:编译时确定变量的数据类型,运行期间不可以改变其结构,比如C++、Java、C#。
动态语言:运行时才确定数据结构和类型,变量使用之前不需要类型声明,比如:JavaScript、PHP、Ruby、Python。
2.TS基础类型和语法
2.1 boolean、string、number、array、null、undefined
// es
let isEnable = false;
let className = 'zhaowa';
let classNum = 2;
let u = undefined;
let n = null;
let classArr = ['basic', 'execute'];
// TS
let isEnable: boolean = false;
let className: string = 'zhaowa';
let classNum: number = 2;
let u: undefined = undefined;
let n: null = null;
// 两种方式: 统一方式 & <>方式
let classArr: string[] = ['basic', 'execute'];
let classArr: Array<string> = ['basic', 'execute'];
不能更改它们的变量类型,否则会直接报错。错误示范:
let myName: String = "zhangsan";
myName = true;//Error:Type 'true' is not assignable to type 'String'.
2.2 tuple - 元组
元组:每一个元素有独立的类型。允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let tupleType: [string, boolean] = ['basic', false];
2.3 枚举
枚举主要分为数字类型枚举、字符串类型枚举、异构。
语法:enum + 变量名,意思是定义了一个叫做“变量名”的枚举类型
优点:以为一组数值赋予友好的名字。
- 数字枚举
含义:枚举成员都是number类型,如果没有对枚举对象中的枚举成员赋值,那么会默认从枚举对象中的第一个枚举成员的值是0,并依次递增。
特点: - 反应映射
- 枚举成员的值可以不用初始化
// 数字型枚举 - 默认从0开始,依次递增
enum Score {
BAD, // 0
NG, // 1
GOOD, // 2
PERFECT, // 3
}
let score: Score = Score.BAD;
// 字符串类型枚举
enum Score {
BAD = 'BAD',
NG = 'NG',
GOOD = 'GOOD',
PERFECT = 'PERFECT',
}
// 反向映射
enum Score {
BAD, // 0
NG, // 1
GOOD, // 2
PERFECT, // 3
}
let scoreName = Score[0]; // BAD
let scoreVale = Score["BAD"]; // 0
// 异构
enum Enum {
A, // 0
B, // 1
C = 'C',
D = 'D',
E = 8,
F, // 9
}
// 面试题:异构类型每一项的枚举值 => js本质实现(手写一个异构枚举的实现)
let Enum;
(function (Enum) {
// 正向
Enum["A"] = 0;
Enum["B"] = 1;
Enum["C"] = "C";
Enum["D"] = "D";
Enum["E"] = 8;
Enum["F"] = 9;
// 逆向
Enum[0] = "A";
Enum[1] = "B";
Enum[8] = "E";
Enum[9] = "F";
})(Enum || (Enum = {}))
2.4 any、unknow、void、nerver
// any - 绕过所有类型检查 => 类型检测和编译筛查取消
let anyValue: any = 123;
anyValue = "anyValue";
anyValue = false;
let value1: boolean = anyValue;
// unknown -绕过赋值检查 => 禁止更改传递
let unknownValue: unknown;
unknownValue = true;
unknownValue = 123;
unknownValue = "unknownValue";
let value1: unknown = unknownValue; // OK
let value2: any = unknownValue; // OK
let value3: boolean = unknownValue; // NOK
// void - 声明返回为空
function voidFunction(): void {
console.log('void');
}
// never - 永不能执行完 or 永远error
function errorGen(msg: string): never {
throw new Error(msg);
}
function infiniteLoop(): never {
while(true) {
// 业务逻辑
}
}
2.5 any和unknown区别
any类型:表示任意类型,即可以赋值给任何类型的变量。使用any类型后,该变量可以进行任何操作而不会触发类型检查,相当于关闭了类型检查器的所有限制。
unknown类型:表示未知类型,即不确定具体类型的变量。与any不同,使用unknown类型后,该变量在没有进行类型检查或类型断言之前,不能被赋值给其他变量或进行任何操作。
使用方式:
- any类型的使用:在需要灵活处理类型的场景下,可以使用any类型。例如,当从第三方库中获取数据时,由于其类型无法确定,可以将其声明为any类型以避免类型错误。
- unknown类型的使用:当我们需要对类型进行安全的操作时,可以使用unknown类型。通常会配合类型判断(type guard)或类型断言来使用。类型判断可以帮助我们在确定类型之后,对变量进行类型转换或操作。
- 注意:使用any类型会绕过类型检查器的所有限制,容易引入类型错误和调试困难。为了保持代码的类型安全性,建议尽可能避免使用any类型,而是使用更精确的类型或通过类型断言将unknown类型转换为其他确定类型。
3.对象的三种状态:object/Object/{}
object - 非原始类型
用来描述后天创建类型的类型描述
interface ObjectConstructor {
create(o: object | null): any;
}
const proto = {};
Object.create(proto);
Object.create(null);
Object.create(undefined); // Error
Object - 原型上的属性,一个集合
// Object.prototype 上的属性
interface Object {
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
}
// 定义了Object类属性
interface ObjectConstructor {
new(value: any): Object;
readonly prototype: Object;
}
{} - 定义空属性对象
// {} - 定义空属性对象
const obj = {};
obj.prop = "props"; // NOK
obj.toString(); // OK
二、接口 - interface
什么是接口?对行为模块的抽象,具体的行为是有类来实现
// 描述对象内容
interface Class {
name: string;
time: number;
}
let zhaowa: Class = {
name: 'typescript',
time: 2
}
// 只读
// readonly 的声明不可改,它是一个只读属性
interface Class {
readonly name: string;
time: number;
}
// 面试题 - JS的引用类型操作不同
// 经常和const对比,const 创建的引用类型是可以改变的,readonly类型的声明是不可被改变的(让一个对象或者数组真正只读)
let arr: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = arr;
ro[0] = 12; // 赋值 - Error
ro.push(5); // 增加 - Error
ro.length = 10; // 长度改写 - Error
arr = ro; // 覆盖 - Error
// ==> 追问:如何拓展interface接口的可描述程度
// 任意可添加属性
interface Class {
readonly name: string;
time: number;
[propName: string]: any;
}
const c1 = { name: "JS" };
const c2 = { name: "browser", time: 1 };
const c3 = { name: "ts", level: 1 };
三、交叉类型 - &
3.1 合并
把一个复杂的东西拆成较简单的小块去做独立的抽象说明。
优点:更加的灵活。
// 合并
interface A {
inner: D;
}
interface B {
inner: E;
}
interface C {
inner: F;
}
interface D {
d: boolean;
}
interface E {
e: string;
}
interface F {
f: number;
}
type ABC = A & B & C;
let abc: ABC = {
inner: {
d: false,
e: 'className',
f: 5
}
};
3.2 合并冲突
interface A {
c: string;
d: string;
}
interface B {
c: number;
e: string;
}
// 声明一个类型,是A和B的合并
type AB = A & B;
let ab: AB;
c - never (永远达不到)
合并的关系是 “且”,如果两个类型不一致 则相交的结果是never。所以c的类型既是string又是number,不存在这样的类型。
四、断言
类型的声明和转换(和编译器做了一个告知交流)
4.1 尖括号形式
语法:<type>+value,尖括号中填写具体的类型。
//anyValue是any类型,在使用时候将其断言为string类型
let anyValue: any = 'hi zhaowa';
let anyLength: number = (<string>anyValue).length;
<string>:执行到这里的时候阶段性的类型是字符串,虽然声明的时候是any类型,但是执行到这个阶段的时候是string
4.2 as声明
语法:value as type,as后跟具体类型。
let anyValue: any = 'hi zhaowa';
let anyLength: number = (anyValue as string).length;
4.3 非空判断 - 只确定不是空
(不能保证执行到这里是字符串,但是能保证执行到这里不是空)
含义:非空断言用!表示,它用来断定某变量一定不是 null 和 undefined。
type ClassTime = () => number;
const start = (ClassTime: ClassTime | undefined) {
// 业务逻辑
// if (额外判断逻辑) {
let time = classTime!(); // 具体类型待定,但是非空确认
// }
}
// 面试题 在ts里可以这样去写吗,非空处理成功了吗
const tsClass: number | undefined = undefined;
const zhaowa: number = tsClass!;
console.log(zhaowa);
// 不行,因为这里使用断言无效。
// 在严格模式下会转义成
const tsClass = undefined;
const zhaowa = tsClass;
console.log(zhaowa); // undefined
//不建议在赋值的时候断言,赋值的时候可以使用逻辑功能做非空限制
4.4 断言的最高境界: 肯定断言
肯定化保证赋值
let score: number;
startClass();
console.log('' + score); // 使用前赋值
function startClass() {
score = 5;
}
// let score!: number; - 提前告知
五、类型守卫
语法规范范围内,额外的确认
多态 - 多种状态(单个变量有多种类型)
- in 定义属性场景下内容的确认
通过类型守卫实现了额外的规范化的流,不能交叉类型合并可以通过导流
interface Teacher {
name: string;
courses: string[];
}
interface Student {
name: string;
startTime: Date;
}
type Class = Teacher | Student;
function startCourse(cls: Class) {
// 进入教室的有可能是老师 有可能是学生
if ('courses' in cls) {
console.log("Courses:" + cls.courses);
}
if ('startTime' in cls) {
console.log("startTime:" + cls.startTime);
}
}
- typeof / instanceof - 类型分类场景下的身份确认
通过类型判断:string进来是学生,(返回为good,perfect),number进来是老师,(返回为 1-5分)
// typeof 判断
function class(name: string, score: string | number) {
if (typeof score === "number") {
return "teacher:" + name + ":" + score;
}
if (typeof score === "string") {
return "student:" + name + ":" + score;
}
}
// instanceof 判断
const getName = (cls: Class) => {
if(cls instanceof Teacher) {
return cls.courses;
}
if(cls instanceof Student) {
return cls.startTime;
}
}
- 自定义类型
// cls是teacher或student中的一种,当前这个函数是用来判断它是不是老师
const isTeacher = function (cls: Teacher | Student): cls is Teacher {
return 'courses' in cls;
}
const getName = (cls: Teacher | Student) => {
if(isTeacher(cls)) {
return cls.courses;
}
}
// 类型别名 & 联合类型
六、TS进阶
6.1 函数重载
6.1.1 什么是函数重载
指在一个作用域中,可以定义多个同名函数,但它们的参数类型或参数个数不同。在调用这个同名函数时,编译器会根据不同的参数类型或参数个数来确定具体调用哪个函数。
class Course {
start(name: number, score: number): number;
start(name: string, score: string): string;
start(name: string, score: number): string;
start(name: number, score: string): string;
start(name: Combinable, score: Combinable) {
if (typeof name === 'string' || typeof score === 'string') {
return 'student:' + name + ':' + score;
}
}
}
// Combinable:多种类型的重载
// 对实际数据的类型做匹配
const course = new Course();
course.start('yunyin', 5);
6.1.2 为什么需要函数重载
提高代码的可读性和可维护性。为同一个功能定义多个重载函数,可以使函数名具有更强的表达力,让代码更加直观和易懂。
提供更灵活的参数选择,使函数使用更加方便。
6.2 泛型
6.2.1 泛型有什么用?
当我们定义一个变量不确定类型的时候有两种解决方式:
1、使用any
缺点:无法获取函数返回值类型,也失去了ts类型保护的优势
2、使用泛型
泛型指的是在定义函数/接口/类型时,不预先指定具体的类型,而是在使用的时候指定类线限制的一种特性
让模块可以支持多种类型数据 - 让类型声明和值一样,可以被赋值和传递
function startClass <T, U>(name: T, score: U) : T {
return name + score;
}
console.log(startClass<String, Number>("yunyin", 5));
// T、U、K - 键值、V - 值、E - 节点、元素
6.3 装饰器
装饰器:一个设计模式的概念,不局限于语言,在任何的实例上去嵌套一个通用的能力。
一个壳:给函数、 类、 类的属性或方法,套了一个通用的壳。
// 配置:tsc --target ES5 --experimentalDecorators
// "experimentalDecorators": true
// 类装饰器
function Zhaowa(target: Function): void {
target.prototype.startClass = function(): void {
// 通用功能
}
}
// 属性装饰器
function propsWrapper(target: Object, key: string) {
// 属性的统一操作
Object.defineProperty(target, key, {
})
}
// 方法装饰器 - target: Object, propertyKey: string, descriptor: TypePropertyDescript
@Zhaowa
class Course {
constructor() {
// 业务逻辑
}
@propsWrapper
public name: string;
@methodDec
}