前言
概述
TypeScript 是一种开源的编程语言,它是 JavaScript 的超集,添加了静态类型检查和一些其他功能。它由微软开发并维护,旨在提供更好的开发工具和更高的可维护性。
特点和优势
- 静态类型检查: TypeScript 强调在开发过程中进行静态类型检查。这意味着可以在编译时捕获和纠正常见的类型错误,减少在运行时出现的错误。类型检查可以提供更好的代码可靠性和可维护性。
- 类型注解和推断: TypeScript 允许开发人员为变量、函数参数、返回值等添加类型注解。这些注解提供了更丰富的类型信息,使代码更具可读性,并帮助 IDE 提供更好的代码补全和错误检查。此外,TypeScript 也能够根据上下文推断出类型,减少了手动注解的工作量。
- ESNext 支持: TypeScript 支持最新的 ECMAScript(JavaScript)语法和功能。这意味着可以使用箭头函数、解构赋值、类、模块等现代 JavaScript 特性,以更直观和简洁的方式编写代码。
- 类型系统和面向对象编程: TypeScript 提供了丰富的类型系统,包括基本类型、联合类型、交叉类型、泛型等。这使得可以更精确地描述数据结构和函数签名,提高代码质量和可维护性。此外,TypeScript 还支持面向对象编程的概念,如类、接口、继承、多态等。
- 工具生态系统: TypeScript 配备了丰富的开发工具和生态系统。它与各种编辑器和 IDE(如 Visual Studio Code)集成良好,并提供了强大的代码补全、重构、调试和错误检查等功能。此外,TypeScript 还支持常见的构建工具和测试框架,如 Webpack、Babel、Jest 等。
- 渐进增强: TypeScript 可以与现有的 JavaScript 代码进行无缝集成。你可以将 TypeScript 文件(.ts 或 .tsx)与 JavaScript 文件(.js 或 .jsx)一起使用,并逐步将 JavaScript 代码迁移到 TypeScript。
基本内容详解
基础类型
TypeScript 支持与 JavaScript 几乎相同的数据类型,并在此基础上扩展了自己的数据类型
JavaScript 数据类型
字符串类型:String 数字类型:Number 布尔类型:Boolean 对象类型:Object 数组类型:Array 函数类型:Function Symbol、undefined、null 其中数组,函数实际也是对象类型,是对象的特殊形式
TypeScript 新增数据类型
void: 用于标识方法返回值的类型,表示该方法没有返回值。
any: 声明为 any 的变量可以赋予任意类型的值。
unknown: 类型安全的any
never: never 类型表示的是那些永不存在的值的类型。
tuple: 元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型必须相同。
枚举: 枚举成员值只读,不可修改。 枚举类型是对 JavaScript 标准数据类型的一个补充
类型声明
基本类型声明
在ts中,当我们定义变量时,需要给他定义一个类型。ts同时支持类型推断,如果定义变量时给变量赋了初始值,ts会默认把变量的类型定义为初始值的类型
// 定义变量并指定类型
let name: string
name = '旺财'
name = 123 // 报错
// 自动推断类型
let name = '旺财'
name = '小黑'
name = 123 // 报错
// 其他基本类型声明
let age: number // 声明数字类型
let isMan: boolean // 声明boolean类型
其他类型声明
数组类型: 数组类型有两种声明方式
// 声明字符串类型的数组
let userList: string[]
// 或者
let userList: Array<string>
元组类型: 元组类型是一种特殊的数组,声明时指定数组长度和数组元素类型
// 声明元组类型
let userList: [string, number]
// 赋值
userList = ['abc', 123]
对象类型
// 定义对象,表示userInfo的类型是一个对象
let userInfo: {
name: string,
age: number
}
// 对象类型定义,赋值时,属性名要完全一致,并且不能添加和缺少属性
userInfo = {
name: '旺财',
age: 3
}
// 定义可选属性
let userInfo: {
name: string,
age?: number
}
// 这样定义表示,赋值时必须包含name属性,age可有可无,但是依旧不能添加其他属性
函数类型
/**
该声明表示setUserInfo为一个函数,并且需要两个参数,并指定参数名,并且
该函数无返回值
*/
let setUserInfo: (name: string, age: number) => void
setUserInfo = (name: string, age: number) => {
console.log(name, age)
}
// 调用函数时,需要根据参数类型传参
setUserInfo('旺财', 3)
// 参数可设置可选参数,这样表示传参是第二个参数可传可不传,不过如果传就必须是number类型
setUserInfo = (name: string, age?: number) => {
console.log(name, age)
}
// 有返回值的函数声明
let setUserInfo: (name: string, age: number) => string
setUserInfo = (name: string, age: number): string => {
return name + number
}
交叉类型: 使用& 符号连接,多个类型合并为一个类型,新的类型具有所有类型的特性。
let userInfo: { name: string } & { age: number }
// 等价于
let userInfo: {
name: string,
age: number
}
联合类型: 取值可以为多种类型中的一种
let a: string | number
a = 'abc' // 正确
a = 123 // 正确
将类型抽离成单独变量
使用type关键字,可以单独定义类型,多个变量可共同使用该类型
// 表示user01,user02的类型均为{ name: string, age: number }
type userInfoType = {
name: string,
age: number
}
let user01: userInfoType
let user02: userInfoType
类
在 TypeScript 中,类(Class)是一种用于创建对象的模板或蓝图。类提供了一种组织数据和相关行为的机制,使得代码更加模块化、可维护和可扩展,定义类使用class关键字
创建类
class Person {
// 内容
}
类的属性
实例属性: 实例属性需要创建类的实例化对象后,通过实例化对象才能读取和修改
class Person {
// 定义实例属性
name: string = '张三'
}
// 实例化对象
const p = new Person()
// 调用属性
console.log(p.name) // 张三
// 修改属性
p.name = '李四'
// 调用属性
console.log(p.name) // 李四
静态属性: 静态属性使用static修饰符修饰,静态属性不需要创建实例,直接通过类名便可读取和修改
class Person {
// 定义静态属性
static sex: string = '男'
}
// 获取静态属性
console.log(Person.sex) // 男
// 修改静态属性
Person.sex = '女'
console.log(Person.sex) // 女
只读属性: 只读属性使用readonly修饰,可修饰实例属性和静态属性,被修饰的属性只能读取,不能修改
class Person {
// 定义只读实例属性
readonly name: string = '张三'
// 定义只读静态属性
static readonly age: number = 10
}
// 获取静态属性
console.log(Person.age) // 10
// 获取实例属性
let p = new Person()
console.log(p.name) // 张三
// 修改静态属性
Person.age = 20 // 报错
// 修改实例属性
p.name = '李四' // 报错
公共属性: 公共属性使用pubic修饰,可修饰实例属性和静态属性,被修饰的属性可以在外部被访问,可以进行读取和修改。在不加任何修饰符的情况下,实例属性和静态属性都默认为公共属性。
class Person {
// 定义公共实例属性
public name: string = '张三'
// 定义公共静态属性
public static age: number = 12
}
// 获取静态属性
console.log(Person.age) // 12
// 获取实例属性
let p = new Person()
console.log(p.name) // 张三
// 修改静态属性
Person.age = 13
console.log(Person.age) // 13
// 修改实例属性
p.name = '李四'
console.log(p.name) // 李四
私有属性: 公共属性使用private修饰,可修饰实例属性和静态属性,被修饰的属性不可以在外部被访问,只可以在类的内部被访问。
class Person {
// 定义私有实例属性
private name: string = '张三'
// 定义私有静态属性
private static age: number = 18
}
// 获取类的实例属性
const p = new Person()
console.log(p.name) // 报错,私有属性不能在类的外部访问
// 获取类的静态属性
console.log(Person.age) // 报错,私有属性不能在类的外部访问
保护属性: 保护属性使用protected修饰,可修饰实例属性和静态属性,被修饰的属性不可以在外部被访问,只可以在类的内部或者其子类中被访问
class Person {
// 定义受保护实例属性
protected name: string = 'jack'
// 定义受保护静态属性
protected static age: number = 18
}
class Student extends Person {
// 在子类中可以访问父类的受保护属性
studentName: string = this.name
// 在子类中可以访问父类的受保护静态属性
studentAge: number = Student.age
}
// 直接获取父类的受保护属性和受保护静态属性
console.log(Person.age) // 报错
const p = new Person()
console.log(p.name) // 报错
// 通过子类获取父类的受保护属性和受保护静态属性
const stu = new Student()
console.log(stu.studentName) // jack
console.log(stu.studentAge) // 18
类的构造函数和this指向
类的构造函数是在创建类的实例时自动调用的特殊方法。构造函数用于初始化类的实例,并设置其初始状态。在 TypeScript 中,构造函数通过 constructor 关键字定义在类中。
初始化属性
class Person {
// 定义实例属性类型
name: string
age: number
// 定义构造函数
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
// 定义构造函数,语法糖写法
class Person1 {
// 定义构造函数
constructor(public name: string, public age: number) {
}
}
// 实例化对象p1
const p1 = new Person('孙悟空', 18)
console.log(p1) // Person { name: '孙悟空', age: 18 }
p1.name = '猪八戒'
p1.age = 28
console.log(p1) // Person { name: '猪八戒', age: 28 }
// 实例化对象p2
const p2 = new Person('猪八戒', 28)
console.log(p2) // Person { name: '猪八戒', age: 28 }
p2.name = '沙和尚'
p2.age = 38
console.log(p2) // Person { name: '沙和尚', age: 38 }
构造函数中this指向: 此时构造函数中的this指向的是通过类创建出的实例
class Person {
// 定义实例属性类型
name: string
age: number
// 定义构造函数
constructor(name: string, age: number) {
this.name = name
this.age = age
console.log(this) // Person {name: "孙悟空", age: 18}
}
}
// 实例化对象p1, 调用new Person('孙悟空', 18)时,console.log(this)中的this指向p1
// 此时打印出的this是Person {name: "孙悟空", age: 18}
const p1 = new Person('孙悟空', 18)
类的实例方法和this指向
实例方法中的this指向的是调用它的实例,谁调用了实例方法,this就指向谁
class Person {
// 定义实例属性类型
name: string
age: number
// 定义构造函数
constructor(name: string, age: number) {
this.name = name
this.age = age
}
// 定义实例方法
sayHello() {
console.log(this) // 调用该方法的实例对象
console.log(this.name + 'hello')
}
}
// 实例化对象
const p1 = new Person('孙悟空', 18)
const p2 = new Person('猪八戒', 28)
// 调用实例方法
p1.sayHello() // 孙悟空hello
p2.sayHello() // 猪八戒hello
类的静态方法和this指向
类定义静态方法后可以直接使用类名.方法名调用,此时静态方法中的this指向类本身,因此在方法内部无法获取实例属性的值,只能获取到静态属性
class Person {
// 定义实例属性
name: string = '孙悟空'
// 定义静态属性
static age: number = 18
// 定义静态方法
static sayHello() {
console.log(this.age, this.name) // 18 undefined
}
}
// 调用静态方法
Person.sayHello()
类的继承
类的继承是面向对象编程中的一个重要概念,它允许一个类从另一个类派生出来,并继承父类的属性和方法。在 TypeScript 中,类的继承通过使用 extends 关键字实现。
继承属性和方法
// 定义一个动物类
class Animal {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
eat() {
console.log(this.name + '正在吃~~~')
}
}
// 定义一个狗类
class Dog extends Animal {
}
// 定义一个猫类
class Cat extends Animal {
}
// 实例化一个狗类
let dog = new Dog('小黑', 3)
dog.eat() // 小黑正在吃~~~
// 实例化一个猫类
let cat = new Cat('小花', 2)
cat.eat() // 小花正在吃~~~
定义自己的方法,重写父类的方法
当我们继承了父类的属性和方法后,我们也可以在自己的类中定义自己的方法,并且可以重写父类继承过来的方法
// 定义一个动物类
class Animal {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
eat() {
console.log(this.name + '正在吃~~~')
}
}
// 定义一个狗类
class Dog extends Animal {
// 定义自己的方法
run() {
console.log(this.name + '正在跑~~~')
}
// 重写父类的方法
eat() {
console.log(this.name + '正在吃狗粮~~~')
}
}
// 定义一个猫类
class Cat extends Animal {
// 定义自己的方法
catchMouse() {
console.log(this.name + '正在抓老鼠~~~')
}
// 重写父类的方法
eat() {
console.log(this.name + '正在吃猫粮~~~')
}
}
// 实例化一个狗类
let dog = new Dog('小黑', 3)
dog.eat() // 小黑正在吃狗粮~~~
dog.run() // 小黑正在跑~~~
// 实例化一个猫类
let cat = new Cat('小花', 2)
cat.eat() // 小花正在吃猫粮~~~
cat.catchMouse() // 小花正在抓老鼠~~~
继承中的super关键字
super 关键字用于在子类中调用父类的构造函数、访问父类的属性和方法
// 定义一个动物类
class Animal {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
eat() {
console.log(this.name + '正在吃~~~')
}
}
// 定义一个狗类
class Dog extends Animal {
// 定义自己的属性
breed: string
constructor(name: string, age: number, breed: string) {
// 调用父类的构造函数
super(name, age)
this.breed = breed
}
// 定义自己的方法
run() {
// 调用父类的方法
super.eat()
console.log(this.breed + '正在跑~~~')
}
}
// 实例化一个狗类
let dog = new Dog('小黑', 3, '哈士奇')
dog.run() // 哈士奇正在跑~~~ 哈士奇正在吃~~~
抽象类和抽象方法
当我们如果创建了一个父类,用来被继承,但是父类本身也是个类,也可以创建自身的实例,此时我们又不想让父类可以创建自身的实例,我们就可以使用抽象类作为父类,抽象类无法创建实例,只能被继承。父类中的方法实际上意义不大,用的时候几乎都需要重写,因此我们可以在父类中不定义具体的方法体,只是定义个抽象方法,专门用来给子类重写,抽象方法只能定义在抽象类中。 抽象类和抽象方法都用abstract修饰
// 定义一个抽象动物类
abstract class Animal {
// 定义属性
name: string
age: number
// 定义构造函数
constructor(name: string, age: number) {
this.name = name
this.age = age
}
// 定义抽象方法
abstract eat(): void
}
// 定义一个狗类
class Dog extends Animal {
// 定义自己的属性
breed: string
constructor(name: string, age: number, breed: string) {
// 调用父类的构造函数
super(name, age)
this.breed = breed
}
// 重写父类的抽象方法
eat() {
console.log(`${this.breed}正在吃~~~`)
}
}
// 实例化一个狗类
let dog = new Dog('小黑', 3, '哈士奇')
dog.eat() // 哈士奇正在吃~~~
// 实例化动物类
// let animal = new Animal('小花', 2) // 报错,抽象类无法实例化
封装类的实例属性
我们如果把实例属性都定义为公共属性,当我们使用类实例化一个对象之后,实例化后的对象可以随意修改实例属性的值,如果一些值是不合法的我们也无法阻止,因此我们需要将属性设置成私有属性,然后通过实例方法来获取和设置该属性,我们可以在实例方法中处理异常逻辑
class Person {
// 属性封装
private _name: string
private _age: number
// 构造函数
constructor(name: string, age: number) {
this._name = name
this._age = age
}
// 属性getter和setter
get name(): string {
return this._name
}
set name(name: string) {
this._name = name
}
get age(): number {
return this._age
}
set age(age: number) {
// 判断年龄是否合法
if (age < 0) {
throw new Error('年龄不能小于0');
}
this._age = age
}
}
const p = new Person('jack', 18)
console.log(p.name) // jack
console.log(p.age) // 18
p.name = 'rose'
p.age = 17
console.log(p.name) // rose
console.log(p.age) // 17
p.age = -1 // 报错:年龄不能小于0
接口
在 TypeScript 中,接口(Interface)用于描述对象的形状(Shape)。接口定义了对象应该具有的属性和方法,并可以用于类型检查和类型推断。定义接口时,使用关键字Interface
使用接口定义类型
接口可以和type一样用来定义变量的类型
interface Person {
name: string,
age: number,
email?: string, // 可选属性
add(x: number, y: number): number, // 定义方法
subtract: (x: number, y: number) => number // 定义方法的另一种方式
readonly x: number // 定义只读属性,只能在创建对象时赋值,不能被修改
}
let p1: Person = {
name: '张三',
age: 18,
add: function (x: number, y: number): number {
return x + y;
},
subtract: function (x: number, y: number): number {
return x - y;
},
x: 10
}
接口的继承
接口和类有相似的继承机制,继承后的接口拥有父接口和自身接口属性的合集
// 定义一个父接口
interface Person {
name: string,
age: number,
email?: string, // 可选属性
}
// 定义一个子接口,继承父接口
interface Student extends Person {
add: (x: number, y: number) => number,
subtract: (x: number, y: number) => number,
}
let p1: Student = {
name: '张三',
age: 18,
add: function (x: number, y: number): number {
return x + y;
},
subtract: function (x: number, y: number): number {
return x - y;
}
}
使用类实现接口
接口可以在定义类的时候限制类的类型,接口在某种意义上与抽象类类似,因此当用接口去限制类的类型的时候,我们也称之为使用该类实现这个接口
// 定义一个接口
interface IPerson {
name: string
age: number
sayHello(): void
}
// 定义一个类
class Person implements IPerson {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
sayHello() {
console.log('大家好,我是' + this.name)
}
}
泛型
在 TypeScript 中,泛型(Generics)是一种在定义函数、类、接口等可重用组件时,允许在声明的时候不指定具体类型,而是在使用时再指定类型的机制。使用泛型可以增加代码的灵活性和复用性,使代码更具通用性,同时保持类型安全。泛型的定义使用尖括号(<>)来表示,通常使用单个大写字母 T 表示泛型类型,当然也可以使用其他大写字母或描述性的名称。
定义泛型函数
当我们定义一个函数时,有时候我们并不知道传入的参数类型是什么,也不知道返回值的类型是什么,只是知道传入的参数类型和返回类型相同,这时候我们就可以使用泛型来定义函数,其中是我们定义的一个泛型,我们指定该函数的参数应该是T类型,返回值也应该是T类型,T可以表示任意类型
function identity<T>(arg: T): T {
return arg
}
使用泛型函数
使用泛型函数时,我们可以指定T的类型也可以直接使用ts的类型推断能力,以下是两种方式
let output = identity<string>("myString")
// 或者
let output = identity("myString")
使用泛型变量
使用泛型创建像identity这样的泛型函数时,编译器要求你在函数体必须正确的使用这个通用的类型。 换句话说,你必须把这些参数当做是任意或所有类型。例如我们在泛型函数中使用一些数组的性质
function identity<T>(arg: T): T {
console.log(arg.length) // Error: T doesn't have .length
return arg
}
此时编辑器会报错,因为如果传入的类型是number类型的话,number是没有length属性的,我们可以做如下修改,此时指定我们的参数类型和返回值类型都是数组,但是数组内的类型是可以任意的,可以是string,number等
function identity<T>(arg: T[]): T[] {
console.log(arg.length) // Error: T doesn't have .length
return arg
}
// 或者
function identity<T>(arg: Array<T>): Array<T> {
console.log(arg.length) // Error: T doesn't have .length
return arg
}
泛型函数的类型
当我们定义了一个泛型函数之后,我们又如何给这个泛型函数定义类型呢,其实泛型函数定义类型的方式和普通函数相同,例如下面我们就定义了一个普通函数类型和泛型函数类型
type functionType = {
normalFunction: (value: string) => string,
genericFunction: <T>(value: T) => T
}