彻底理解TypeScript对象语法

对象

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: '深圳'}}

索引签名

用于描述对象属性的键和值不确定的情况,可以定义对象允许的键和值类型,适用于键值对数量和名称不固定的对象

  • 键类型只能是 stringnumbersymbol 或模板字符串值的类型可以是任意指定的类型

    ts 复制代码
    interface 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
  • 索引可以同时存在多个,但数字索引的值类型必须是字符索引(包含模板字符串)值类型的子类型

    ts 复制代码
    interface 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
    }
  • 可以在包含索引签名的接口中定义具体的属性,但这些属性的值类型必须与索引签名的值类型一致或兼容

    ts 复制代码
    interface 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 支持使用 getset 关键字来定义属性的访问器(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 的关系。猫拥有跑(可以定义一个单独的接口)、爬树(可以定义一个单独的接口) 的行为

相关推荐
未命名冀1 小时前
微服务day11-微服务面试
微服务·面试·架构
我也有在努力1 小时前
解决Electron拖拽窗口点击事件失效问题
前端·javascript·vue.js·typescript·electron·vue
仲夏那片海1 小时前
spring web项目中常用的注解
java·前端·spring
一棵开花的树,枝芽无限靠近你2 小时前
【element-tiptap】Tiptap编辑器核心概念----结构篇
前端·笔记·学习·编辑器
清酒伴风(面试准备中......)2 小时前
Spring基础——针对实习面试
java·spring·面试·实习
苦逼的猿宝2 小时前
React中使用echarts写出3d旋转扇形图
前端·react.js·echarts
我要学编程(ಥ_ಥ)2 小时前
速通前端篇 —— CSS
前端·css
黑色的糖果2 小时前
npm上传自己封装的插件(vue+vite)
前端·vue.js·npm·vite
祭の3 小时前
HttpServletResponse响应对象讲解(笔记)
java·前端·笔记
前端双越老师3 小时前
程序员设计不出精美的 UI 界面?让 V0 来帮你
前端·react.js·aigc