存取器

存取器是类中特殊的属性访问方式,通过 getset 关键字定义。它们让我们可以在读取或设置属性时执行额外的逻辑,比如数据验证、格式转换或触发副作用。

基本语法

使用 get 定义读取器,使用 set 定义设置器。存取器看起来像属性,但实际是方法调用。

class Person {
  private _name: string = ""

  get name(): string {
    return this._name
  }

  set name(value: string) {
    if (value.length > 0) {
      this._name = value
    }
  }
}

const person = new Person()
person.name = "张三"
console.log(person.name)

name 看起来像普通属性,但实际通过存取器访问。设置时会检查值是否为空,读取时返回内部存储的 _name

数据验证

存取器最常见的用途是在设置属性时进行验证,确保数据的有效性。

class User {
  private _age: number = 0

  get age(): number {
    return this._age
  }

  set age(value: number) {
    if (value < 0) {
      console.log("年龄不能为负数")
      return
    }
    if (value > 150) {
      console.log("年龄不合理")
      return
    }
    this._age = value
  }
}

const user = new User()
user.age = 25
console.log(user.age)
user.age = -5
user.age = 200

设置年龄时会检查范围,无效的值会被拒绝。这比直接暴露属性更安全。

计算属性

存取器可以返回计算后的值,而不需要存储实际数据。

class Rectangle {
  width: number
  height: number

  constructor(width: number, height: number) {
    this.width = width
    this.height = height
  }

  get area(): number {
    return this.width * this.height
  }

  get perimeter(): number {
    return 2 * (this.width + this.height)
  }
}

const rect = new Rectangle(10, 20)
console.log(rect.area)
console.log(rect.perimeter)

areaperimeter 是计算属性,每次访问时都会重新计算。不需要单独存储这些值,它们总是与 widthheight 保持同步。

只读存取器

只定义 get 不定义 set,就创建了只读属性。

class Counter {
  private _count: number = 0

  get count(): number {
    return this._count
  }

  increment(): void {
    this._count++
  }

  reset(): void {
    this._count = 0
  }
}

const counter = new Counter()
console.log(counter.count)
counter.increment()
counter.increment()
console.log(counter.count)
counter.reset()
console.log(counter.count)

count 是只读属性,外部无法直接修改,只能通过 incrementreset 方法改变。

延迟初始化

存取器可以实现延迟初始化,只有在第一次访问时才创建资源。

class Database {
  private _connection: any = null

  get connection(): any {
    if (!this._connection) {
      console.log("建立数据库连接...")
      this._connection = { connected: true }
    }
    return this._connection
  }

  query(sql: string): any {
    return this.connection.query(sql)
  }
}

const db = new Database()
console.log("数据库实例创建")
console.log(db.connection)

数据库连接在第一次访问 connection 属性时才创建,避免了不必要的资源消耗。

格式转换

存取器可以在读写时进行格式转换。

class Product {
  private _price: number = 0

  get price(): string {
    return ${this._price.toFixed(2)}`
  }

  set price(value: string) {
    const num = parseFloat(value.replace(/[^\d.]/g, ""))
    if (!isNaN(num) && num >= 0) {
      this._price = num
    }
  }

  get priceNumber(): number {
    return this._price
  }
}

const product = new Product()
product.price = "299.99"
console.log(product.price)
console.log(product.priceNumber)
product.price = "¥599.00"
console.log(product.price)

price 存取器处理字符串和数字之间的转换,内部存储数字,外部显示格式化的字符串。

触发副作用

存取器可以在设置值时触发其他操作。

class ViewModel {
  private _data: any = {}
  private _listeners: Array<(data: any) => void> = []

  get data(): any {
    return { ...this._data }
  }

  set data(value: any) {
    this._data = value
    this.notify()
  }

  subscribe(listener: (data: any) => void): void {
    this._listeners.push(listener)
  }

  private notify(): void {
    this._listeners.forEach(listener => listener(this._data))
  }
}

const vm = new ViewModel()
vm.subscribe((data) => console.log("数据变化:", data))
vm.data = { name: "张三" }
vm.data = { name: "李四", age: 25 }

每次设置 data 时,都会通知所有订阅者。这是响应式编程的基础模式。

存取器与接口

接口可以定义存取器的签名。

interface IProduct {
  price: number
  discount: number
  finalPrice: number
}

class Product implements IProduct {
  price: number
  discount: number = 0

  constructor(price: number) {
    this.price = price
  }

  get finalPrice(): number {
    return this.price * (1 - this.discount)
  }
}

const product = new Product(100)
product.discount = 0.2
console.log(product.finalPrice)

接口定义了 finalPrice 属性,类通过存取器实现这个属性的计算逻辑。

存取器的性能考虑

存取器在每次访问时都会执行,如果有复杂计算,可能影响性能。

class DataProcessor {
  private _data: number[] = []
  private _cachedSum: number | null = null

  get data(): number[] {
    return [...this._data]
  }

  set data(value: number[]) {
    this._data = value
    this._cachedSum = null
  }

  get sum(): number {
    if (this._cachedSum === null) {
      console.log("计算总和...")
      this._cachedSum = this._data.reduce((a, b) => a + b, 0)
    }
    return this._cachedSum
  }
}

const processor = new DataProcessor()
processor.data = [1, 2, 3, 4, 5]
console.log(processor.sum)
console.log(processor.sum)

使用缓存可以避免重复计算。sum 存取器只在数据变化后第一次访问时计算,之后返回缓存值。

小结

存取器提供了对属性访问的精细控制。通过 getset,我们可以在读写属性时执行验证、转换、计算等操作。存取器让代码更加健壮,也更容易维护。在实际开发中,合理使用存取器可以封装复杂的逻辑,提供简洁的属性接口。