对象
TypeScript
允许为对象的每个属性指定类型,并可以结合接口、类型别名等工具来定义复杂的对象结构
类型表示
ts
/* 基本表示 */
let obj1: { name: string, age: number } = {
name: 'obj1',
age: 8
}
console.log(obj1) // {name: 'obj1', age: 8}
/* 可选只读 */
let obj2: {readonly name: string, age?: number} = {
name: 'obj2'
}
// obj2.name = 'obj' // 无法为"name"赋值,因为它是只读属性
console.log(obj2.name, obj2) // obj2 {name: 'obj2'}
/* 嵌套 */
let obj3: {name: string, age: number, address: { city: string; zipCode?: number }} = {
name: 'obj3',
age: 18,
address: {
city: '北京'
}
}
console.log(obj3) // {name: 'obj3', age: 18, address: {city: '北京'}}
/* 接口定义 interface */
interface IObj4 {
readonly name: string,
age: number
address: { city: string; zipCode?: number }
}
let obj4: IObj4 = {
name: 'obj4',
age: 28,
address: {
city: '北京'
}
}
console.log(obj4) // {name: 'obj4', age: 28, address: {city: '北京'}}
/* 类型别名定义 type */
interface obj5Type {
readonly name: string,
age: number
address: { city: string; zipCode?: number }
}
let obj5: obj5Type = {
name: 'obj5',
age: 38,
address: {
city: '深圳'
}
}
console.log(obj5) // name: 'obj5', age: 38, address: {city: '深圳'}}
索引签名
用于描述对象属性的键和值不确定的情况,可以定义对象允许的键和值类型,适用于键值对数量和名称不固定的对象
-
键类型只能是
string
、number
、symbol
或模板字符串 ,值的类型可以是任意指定的类型tsinterface IInfo1 { [index: number]: number } const indexArr: IInfo1 = [123, 321] // 要满足可以通过number索引取到number,对象不满足 console.log(indexArr) interface IInfo2 { [key: string]: string } const keyObj: IInfo2 = { name: 'keyObj' } console.log(keyObj) // 要满足可以通过string索引拿到的值也是string const mySymbol = Symbol('mySymbol'); interface IInfo3 { [fn: symbol]: () => void } const symObj: IInfo3 = { [mySymbol]: function (){} }; console.log(symObj[mySymbol]); // 输出: 函数 type templateType = `prefix_${string}`; // 必须拼接变量,只写字符串会报错 interface IInfo4 { [template: templateType]: number; } const templateObj: IInfo4 = { 'prefix_name': 42, }; console.log(templateObj['prefix_name']); // 输出: 42
-
索引可以同时存在多个,但数字索引的值类型必须是字符索引(包含模板字符串)值类型的子类型
tsinterface IInfo5 { [index: number]: number /* 下面注释的一行代码会报错: "number"索引类型"number"不能分配给"string"索引类型"string" 因为: 所有的数字类型都是会转成字符串类型去对象中获取内容 当是一个数字的时候,既要满足通过number去拿到的内容, 又不会和string拿到的结果矛盾 比如当我们使用number索引签名取值时,比如obj[1]取到的是number,相当于obj['1']取到的也应该是number 但使用string索引签名取值时,比如obj['age']取到的是string, 但obj['age']取到的值可能和obj[1]取到的是同一个值,他怎么可能是数字又是string,所以报错 */ // [key: string]: string [key: string]: number [fn: symbol]: () => void [template: templateType]: number; } const mulObj: IInfo5 = { age: 123, [mySymbol]: function (){}, 'prefix_name': 42 }
-
可以在包含索引签名的接口中定义具体的属性,但这些属性的值类型必须与索引签名的值类型一致或兼容
tsinterface IInfo6 { [index: number]: number [key: string]: number [fn: symbol]: () => void [template: templateType]: number; // checked: boolean // 报错 // str: string // 报错 sum: number } const mulAttrObj: IInfo6 = { sum: 20 // 必须要有此属性 }
类
在早期的JavaScript
开发中(ES5
)我们需要通过函数和原型链来实现类和继承,从ES6
开始,引入了class
关键字,可以更加方便的定义和使用类。TypeScript
作为JavaScript
的超集,也是支持使用class
关键字的,并且还可以对类的属性和方法等进行静态类型检测
定义
使用class
关键字来定义一个类,可以声明类的属性,在类的内部声明类的属性以及对应的类型,基本和JavaScript
是一样的
-
如果类型没有声明默认是
any
的,可以给属性设置初始化值 -
在默认的
strictPropertyInitialization
模式下面我们的属性是必须初始化的,没有初始化编译就会报错 -
如果在
strictPropertyInitialization
模式下不希望给属性初始化,可以使用name!: string
语法 -
类有自己的构造函数
constructor
,通过new
关键字创建 实例时,构造函数会被调用 -
构造函数不需要返回任何值,默认返回当前创建出来的实例
-
类中可以有自己的函数,定义的函数称之为方法
使用
ts
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): void {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const person = new Person("John", 30);
person.greet(); // 输出:Hello, my name is John and I am 30 years old.
继承
ts
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): void {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
class Employee extends Person {
jobTitle: string;
constructor(name: string, age: number, jobTitle: string) {
super(name, age); // 调用父类的构造函数
this.jobTitle = jobTitle;
}
describe(): void {
console.log(`${this.name} works as a ${this.jobTitle}`);
}
}
const employee = new Employee("Jane", 25, "Software Developer");
employee.greet(); // 输出:Hello, my name is Jane and I am 25 years old.
employee.describe(); // 输出:Jane works as a Software Developer.
成员修饰符
在TypeScript
中,类的属性和方法支持三种修饰符:public、private、protected
-
public
修饰的是在任何地方可见、公有的属性或方法 ,默认编写的属性就是public
的 -
private
修饰的是仅在同一类中可见、私有的属性或方法 -
protected
修饰的是仅在类自身及子类中可见、受保护的属性或方法
ts
class Shape {
public width: number
private _height: number // 私有通常使用_表示
constructor (width: number, height: number){
this.width = width
this._height = height
}
protected getArea(): number {
return this.width * this._height
}
}
let s = new Shape(10, 20)
console.log(s.width)
console.log(s._height) // 属性"_height"为私有属性,只能在类"Shape"中访问
console.log(s.getArea()) // 属性"getArea"受保护,只能在类"Shape"及其子类中访问
class circle extends Shape {
r: number
constructor(width: number, height: number, r: number) {
super(width, height)
this.r = r
}
getWhr(){
return this.getArea() * this.r // getArea()可在子类中访问
}
}
参数属性
- 可以把一个构造函数参数转成一个同名同值的类属性,这些就被称为参数属性(
parameter properties
) - 可以通过在构造函数参数前添加一个可见性修饰符
public private protected
或者readonly
来创建参数属性,最后这些类属性字段也会得到这些修饰符
ts
class Shape {
constructor (public width: number, private height: number){
this.width = width
this.height = height
}
protected getArea(): number {
return this.width * this.height
}
}
let s = new Shape(10, 20)
console.log(s.width)
console.log(s.height) // 属性"height"为私有属性,只能在类"Shape"中访问
console.log(s.getArea()) // 属性"getArea"受保护,只能在类"Shape"及其子类中访问
class circle extends Shape {
constructor(width: number, height: number, readonly r: number) {
super(width, height)
this.r = r
}
getWhr(){
return this.getArea() * this.r // getArea()可在子类中访问
}
}
getters / setters
TypeScript
支持使用 get
和 set
关键字来定义属性的访问器(getter
)和设置器(setter
),当有一些私有属性是不能直接访问的,或者某些属性想要监听它的获取(getter
)和设置(setter
)的过程,这个时候可以使用存取器
ts
class Person {
private _age: number;
constructor(age: number) {
this._age = age;
}
get age(): number {
return this._age;
}
set age(value: number) {
if (value > 0) {
this._age = value;
}
}
}
const person = new Person(30);
console.log(person.age); // 输出:30 这时可以通过age拿到私有属性_age
person.age = 35;
console.log(person.age); // 输出:35
类的类型
抽象类 abstract
允许你定义一些具有共同功能的类,但不能直接实例化 。抽象类通常用于定义共享的结构和行为,而具体的实现则交由继承它的子类来完成
-
抽象方法必须存在于抽象类中 ,抽象类是使用
abstract
声明的类 -
抽象类是不能被实例化 (也就是不能通过
new
创建) -
抽象类可以包含抽象方法,也可以包含属性、方法和构造函数
-
有抽象方法的类,必须是一个抽象类
-
抽象方法必须被子类实现,否则该类必须是一个抽象类
ts
abstract class Shape {
// 抽象方法,没有方法体,必须在子类中实现,每个形状可以实现自己的获取面积方法
abstract getArea(): number
constructor(public w: number, public h: number) {
this.w = w
this.h = h
}
// 具体方法,子类可以直接继承使用
divide(): void {
console.log("divide...");
}
}
class Circle extends Shape {
constructor(w: number, h: number, private r: number){
super(w, h)
this.r = r
}
getArea(): number { // 必须实现
this.divide() // divide...
return this.w * this.h * this.r
}
}
const s = new Shape(10, 20) // 报错:无法创建抽象类的实例
const c = new Circle(10, 20, 30)
console.log(c.getArea()) // 6000
类实现接口
-
通过接口(
interface
),你可以定义对象应该具有哪些属性和方法,然后通过实现接口的类来确保这些类具有所需的行为和结构 -
使用
implements
关键字,类可以实现接口。实现接口的类必须提供接口中定义的所有属性和方法的具体实现 -
如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入
ts
interface IJump {
jump: () => void
}
interface IEat {
eat: () => void
}
class Action implements IJump, IEat {
constructor(public name: string, public age: number) {
this.name = name
this.age = age
}
jump() {
console.log(`${this.name}爱jump`)
}
eat() {
console.log(`${this.name}爱eat`)
}
}
const a = new Action('action', 18)
a.jump() // action爱jump
a.eat() // action爱eat
抽象类和接口区别
抽象类在很大程度上和接口会有点类似:都可以在其中定义一个方法,让子类或实现类来实现对应的方法,但他们还是有区别的:
-
接口(interface) 只定义结构,不包含任何实现细节,所有的实现都必须由类来完成
-
抽象类可以定义部分实现,并且允许子类继承这些实现
-
接口 只可以定义抽象的方法和属性 ,而 抽象类 可以包含具体的方法和属性
-
一个类可以实现多个接口,但只能继承一个抽象类
-
抽象类是对事物的抽象 ,表达的是
is a
的关系。猫是一种动物(动物就可以定义成一个抽象类) -
接口是对行为的抽象 ,表达的是
has a
的关系。猫拥有跑(可以定义一个单独的接口)、爬树(可以定义一个单独的接口) 的行为