TypeScript(TS)是 JavaScript 的超集,在 JS 基础上添加类型支持,是微软开发的开源编程语言,可在任何运行 JavaScript 的地方运行,如let age1: number = 18(TS 代码)和let age2 = 18(JS 代码)对比,TS 有明确类型标注。
2. 添加类型支持的原因
JS 类型系统存在缺陷,多数错误为类型错误,影响开发效率。TS 属于静态类型编程语言,在编译期做类型检查;JS 属于动态类型编程语言,执行期做类型检查。TS 能在编译时(执行前)发现错误,配合开发工具可在编写代码时发现错误,减少找 Bug 和改 Bug 时间。
3. 优势
更早发现错误,提升开发效率;提供代码提示,增强开发体验;提升代码可维护性,便于重构;支持最新 ECMAScript 语法;有类型推断机制,降低使用成本。在多个前端框架中广泛应用,是大中型前端项目首选编程语言。
4. 初体验
环境搭建:Node.js 和浏览器只识别 JS 代码,需安装typescript包(npm i -g typescript)将 TS 转化为 JS,通过tsc -v验证安装,tsc命令实现转化。
运行 TS 代码
传统方式:创建.ts文件,用tsc命令编译生成.js文件,再用node命令执行。
简化方式:安装ts-node包(npm i -g ts-node),用ts-node命令直接运行 TS 文件,内部自动完成转化。
二、常用类型系统
1. 类型注解与类型推论
类型注解:为变量、函数参数、返回值、对象属性和方法等添加类型约束,如let age: number = 18中: number为类型注解,约定变量类型,赋值不符会报错。
//Typescript代码中有明确的类型,即:number(数值类型)
let age:number=23;
//JavaScript代码,无明确的类型
let age2=23;
类型推论:在声明变量并初始化或决定函数返回值时,TS 自动推断类型,可省略类型注解,如let age = 18(TS 推断age为number类型)。推荐省略能省略的类型注解,利用 VSCode 提示查看类型。
2. 常用基础类型
原始类型:包括number、string、boolean、null、undefined、symbol,书写与 JS 中类型名称一致,如Let age: number = 18。
数组类型:推荐number[]写法,也可用Array<string>。元素包含多种类型时,用联合类型表示,如Let arr: (number | string)[] = [1, 'a', 3, 'b'],注意与 JS 中或(||)的区别。
//创建数组的方式
const arr1:string[]=[]
const arr:number[]=[1,2,3]
const arr2:Array<string> = ['basketball','football']
//数组的使用
const arr:number[]=[1,2,3,4,5]//创建一个number类型的数组
const arr1:String[]=["wcr","zs","fcc"]//创建一个string类型的数组
console.log(arr1.length);//得到长度3
console.log(arr1[0]);//取值,得到wcr
//遍历数组arr,从0号索引开始,最大的长度是length-1
for(let i:number=0;i<arr.length-1;i++){
console.log(arr[i]);
}
类型别名(自定义类型):使用type关键字为复杂类型创建别名简化使用,如type CustomArray = (number | string)[],之后可直接用别名作为变量类型注解。可以提高开发效率,避免多次写重复的代码。
//类型别名 type
type numtype= number|string|null|Array<String|number>
//联合类型
const num:numtype=123
const A:numtype='123'
const B:numtype=null
const C:numtype=[1,2,3]
3. 函数类型
参数与返回值类型指定
单独指定:
同时指定(函数表达式):
特殊情况
无返回值时函数返回值类型为void
function greet(name:string):void{
console.log('hello',name);
}
可选参数:参数可传可不传时,在参数名后加?,且必须在参数列表末尾
4. 对象类型
是用来描述对象结构的
解释:
1. 直接使用 {} 来描述对象结构。属性采用 属性名: 类型 的形式;方法采用 方法名(): 返回值类型 的形式。
2. 如果方法有参数,就在方法名后面的小括号中指定参数类型(比如: greet(name: string): void )。
3. 在一行代码中指定对象的多个属性类型时,使用 ;(分号)来分隔。
l 如果一行代码只指定一个属性类型(通过换行来分隔多个属性类型),可以去掉 ;(分号)。
l 方法的类型也可以使用箭头函数形式(比如:{ sayHi: () => void })。
5. 接口:
一般使用interface表示接口,是用来达到复用的目的。
interface Iperson{
name:string;
age:number;
sayHi():void;
}
let person:Iperson={
name:'jack',
age:12,
sayHi(){
console.log('hi');
}
}
//解释:
//1. 使用 interface 关键字来声明接口。
//2. 接口名称(比如,此处的 IPerson),可以是任意合法的变量名称。
//3. 声明接口后,直接使用接口名称作为变量的类型。
//4. 因为每一行只有一个属性类型,因此,属性类型后没有 ;(分号)。
//接口和类型别名的区别:二者都是给对象指定类型的;但是接口是给对象指定类型的,类型别名可以为任一类型指定别名。
同时,接口也具有继承的特性,使用extends关键字。
6. 元组
用于确切知道数组元素个数和特定索引对应类型的场景
7. 类型断言
当 TS 推断类型不能满足需求时,使用类型断言指定更具体类型
//定义一个cat的接口
interface cat{
name:string,
age:number,
run:()=>void
}
//定义一个dog的接口
interface dog{
name:string,
eat:()=>void
}
//将一个父类断言为子类,首先animals
//可以是cat或者dog类型。
function animal(animals:cat|dog){
//将animals断言为cat类型,那它具有cat
//的属性和方法。
console.log((animals as cat).age);
}
//此时name为dog的类型就具有cat的age属性,为2
console.log({name:"dog",age:2});
8. 字面量类型
let str='Hello TS'
function changeDirection(direction: 'left' | 'right'|'top'|'bottom') {
console.log(direction);
}
总结:特定的字符串、数字等字面量可作为类型,如str类型为'Hello TS'。常与联合类型配合使用,表示一组明确的可选值列表,如函数参数direction只能取'left' |'right'|'top'|'bottom'中的值,相比string类型更精确严谨。
9. 枚举
enum Direction1{//默认值从0开始,递增
up=8,
down,
left,
right
}
enum Direction2{
//字符串枚举,枚举成员的值是字符串,
// 注意: 字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值。
up='UP',
down='down',
left='left',
right='right'
}
const a:Direction1=Direction1.up
console.log(a);//8
const c:Direction2=Direction2.up
console.log(c);/UP
总结:
1. 使用enum关键字定义枚举
2. 约定枚举名称、枚举中的值以大写字母开头
3. 枚举中的多个值之间通过 ,(逗号)分隔
4. 定义好枚举后,形参直接使用枚举名称作为类型注解
5. 实参的值可以是枚举 Direction 成员的任意一个,接通过点(.)语法访问枚举的成员
6. 从输出结果为0,我们可以知道表示枚举的值是会携带默认值的,从 0 开始自增的数值
7. 通过扩展1能知道数字的起始值可以自定义,并且自增只针对数字类型
8. 枚举中的成员也可以自定义初始化值
10. any 类型
原则上不推荐使用,会使 TS 失去类型保护优势。
解释:以上操作无类型错误提示,但可能存在错误。其他隐式具有any类型的情况有声明变量不提供类型也不提供默认值、函数参数不加类型,应尽量避免使用,除非临时用于 “避免” 书写复杂类型。
11. typeof
在 TS 中,typeof可在类型上下文中引用变量或属性的类型(类型查询),用于根据已有变量的值获取其类型简化类型书写。
解释:
1. 使用 typeof 操作符来获取变量 p 的类型,结果与第一种(对象字面量形式的类型)相同。
2. typeof 出现在 类型注解的位置 (参数名称的冒号后面) 所处的环境就在类型上下文 (区别于 JS 代码)。
3. 注意:typeof 只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)。
三、高级类型特性
1. class
TS 全面支持class关键字,为其添加类型注解和其他语法(如可见性修饰符等)。
实例属性初始化:
class Person{
age:number;
gender='男'
//gender:string='男'
}
//解释:
//1. 声明成员 age,类型为 number(没有初始值)。
//2. 声明成员 gender,并设置初始值,此时,可省略类型注解(TS 类型推论 为 string 类型)。
构造函数:成员初始化后才能在构造函数中通过this访问,构造函数需指定类型注解,无返回值类型。
class Person{
age:number
gender:string
constructor(age:number,gender:string){
this.age=age
this.gender=gender
}
}
实例方法:方法类型注解与函数用法相同
类继承:有extends(继承父类)和implements(实现接口)两种方式。
extends实现继承,子类继承父类后拥有父类和子类所有属性和方法,
implements使类必须实现接口中指定的方法和属性。
类成员可见性:包括public(公有的,默认可见性)、protected(受保护的,仅对声明所在类和子类中(非实例对象)可见)、private(私有的,只在当前类中可见)和readonly(只读修饰符,防止在构造函数之外对属性赋值,只能修饰属性不能修饰方法)。
2. 泛型
可在保证类型安全前提下让函数等与多种类型一起工作实现复用,常用于函数、接口、class中。
创建泛型函数:语法为在函数名后加<>尖括号,尖括号内为类型变量(可自定义名称),类型变量作为函数参数和返回值类型,表示参数和返回值具有相同类型,如function id<Type>(value: Type): Type { return value; }。
调用泛型函数:可在函数名后<>指定具体类型,也可省略(TS 会根据传入实参自动推断类型变量类型),如const num = id<number>(10); const str = id('a'); let num = id(10);,推荐省略简化调用,但编译器无法推断或推断不准确时需显式传入类型参数。
泛型约束:默认泛型函数类型变量可代表多种类型,可能无法访问属性,需添加约束收缩类型,可指定更具体类型(如Type[])或添加约束(通过extends关键字使用接口约束类型变量,要求传入类型必须具有指定属性),泛型的类型变量可多个且相互约束,如function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) { return obj[key]; }。
泛型接口:接口名称后加<类型变量>变为泛型接口,类型变量对接口所有成员可见,使用时需显式指定具体类型,如interface IdFunc<Type> { id: (value: Type) => Type; ids: () => Type; } let obj: IdFunc<number> = { id(value) { return value; }, ids() { return [1, 3, 5]; } },JS 中的数组在 TS 中是泛型接口,TS 会根据数组类型自动设置类型变量。
泛型类:class名称后加<类型变量>变为泛型类,创建实例时需在类名后<>指定明确类型,如class GenericNumber<NumType> { defaultValue: NumType; add: (x: NumType, y: NumType) => NumType; } const myNum = new GenericNumber<number>()。
泛型工具类型:TS 内置基于泛型实现的工具类型,如Partial<Type>(将Type所有属性设为可选)、Readonly<Type>(将Type所有属性设为只读)、Pick<Type, Keys>(从Type中选一组属性构造新类型)、Record<Keys, Type>(构造对象类型,属性键为Keys,属性类型为Type)。