Skip to content

访问器(Getters 和 Setters)

概述

访问器(Accessors)是 TypeScript 类中的一个重要特性,使用 getset 关键字可以创建属性的访问器方法。Getter 用于读取属性值,Setter 用于设置属性值。访问器允许我们在访问或修改属性时执行额外的逻辑,如数据验证、计算属性、副作用处理等。这使得类的属性访问更加灵活和安全。

基本语法

Getter(读取器)

使用 get 关键字定义属性的读取器,当访问该属性时会自动调用 getter 方法:

typescript
class Circle {
  private _radius: number;
  
  constructor(radius: number) {
    this._radius = radius;
  }
  
  // Getter:读取半径
  get radius(): number {
    return this._radius;
  }
  
  // Getter:计算面积
  get area(): number {
    return Math.PI * this._radius * this._radius;
  }
  
  // Getter:计算周长
  get circumference(): number {
    return 2 * Math.PI * this._radius;
  }
}

const circle = new Circle(5);
console.log(circle.radius);        // 5(访问 getter)
console.log(circle.area);          // 78.53981633974483(计算属性)
console.log(circle.circumference); // 31.41592653589793(计算属性)

Setter(设置器)

使用 set 关键字定义属性的设置器,当给该属性赋值时会自动调用 setter 方法:

typescript
class Temperature {
  private _celsius: number;
  
  constructor(celsius: number) {
    this._celsius = celsius;
  }
  
  // Getter:获取摄氏度
  get celsius(): number {
    return this._celsius;
  }
  
  // Setter:设置摄氏度(带验证)
  set celsius(value: number) {
    if (value < -273.15) {
      throw new Error("Temperature cannot be below absolute zero");
    }
    this._celsius = value;
  }
  
  // Getter:获取华氏度(计算属性)
  get fahrenheit(): number {
    return (this._celsius * 9) / 5 + 32;
  }
  
  // Setter:设置华氏度(自动转换为摄氏度)
  set fahrenheit(value: number) {
    this._celsius = ((value - 32) * 5) / 9;
  }
}

const temp = new Temperature(25);
console.log(temp.celsius);     // 25
console.log(temp.fahrenheit);  // 77

temp.celsius = 30;             // 使用 setter
console.log(temp.celsius);     // 30
console.log(temp.fahrenheit);  // 86

temp.fahrenheit = 100;         // 使用 setter(自动转换)
console.log(temp.celsius);     // 37.77777777777778

提示

访问器使用起来就像普通属性一样,但实际上是方法调用。Getter 和 Setter 可以分别定义,也可以只定义其中一个。

基本用法

只读属性(只有 Getter)

如果只定义 getter 而不定义 setter,该属性就变成了只读属性:

typescript
class User {
  private _id: number;
  private _name: string;
  private _email: string;
  
  constructor(id: number, name: string, email: string) {
    this._id = id;
    this._name = name;
    this._email = email;
  }
  
  // 只读属性:ID 不能修改
  get id(): number {
    return this._id;
  }
  
  // 可读写属性:姓名
  get name(): string {
    return this._name;
  }
  
  set name(value: string) {
    if (value.trim().length === 0) {
      throw new Error("Name cannot be empty");
    }
    this._name = value;
  }
  
  // 可读写属性:邮箱(带验证)
  get email(): string {
    return this._email;
  }
  
  set email(value: string) {
    if (!value.includes("@")) {
      throw new Error("Invalid email address");
    }
    this._email = value;
  }
}

const user = new User(1, "Alice", "alice@example.com");
console.log(user.id);    // 1
console.log(user.name);  // "Alice"

user.name = "Bob";       // ✅ 可以修改
user.email = "bob@example.com"; // ✅ 可以修改

// ❌ 错误:ID 是只读的
// user.id = 2; // Error: Cannot set property id of #<User> which has only a getter

数据验证

Setter 非常适合用于数据验证,确保属性值符合要求:

typescript
class BankAccount {
  private _balance: number = 0;
  private _accountNumber: string;
  
  constructor(accountNumber: string) {
    this._accountNumber = accountNumber;
  }
  
  get balance(): number {
    return this._balance;
  }
  
  // Setter 中不能直接设置余额,需要通过方法控制
  // 但可以用于验证其他属性
  
  get accountNumber(): string {
    return this._accountNumber;
  }
  
  set accountNumber(value: string) {
    if (!/^[A-Z]{2}\d{8}$/.test(value)) {
      throw new Error("Account number must be in format: XX12345678");
    }
    this._accountNumber = value;
  }
  
  public deposit(amount: number): void {
    if (amount <= 0) {
      throw new Error("Deposit amount must be positive");
    }
    this._balance += amount;
  }
  
  public withdraw(amount: number): boolean {
    if (amount <= 0) {
      throw new Error("Withdrawal amount must be positive");
    }
    if (amount > this._balance) {
      return false;
    }
    this._balance -= amount;
    return true;
  }
}

const account = new BankAccount("AB12345678");
account.deposit(1000);
console.log(account.balance); // 1000

// ✅ 正确:符合格式的账户号
account.accountNumber = "CD87654321";

// ❌ 错误:不符合格式
// account.accountNumber = "invalid"; // Error: Account number must be in format: XX12345678

计算属性

Getter 可以用于创建计算属性,这些属性的值是根据其他属性动态计算的:

typescript
class Rectangle {
  private _width: number;
  private _height: number;
  
  constructor(width: number, height: number) {
    this._width = width;
    this._height = height;
  }
  
  get width(): number {
    return this._width;
  }
  
  set width(value: number) {
    if (value <= 0) {
      throw new Error("Width must be positive");
    }
    this._width = value;
  }
  
  get height(): number {
    return this._height;
  }
  
  set height(value: number) {
    if (value <= 0) {
      throw new Error("Height must be positive");
    }
    this._height = value;
  }
  
  // 计算属性:面积
  get area(): number {
    return this._width * this._height;
  }
  
  // 计算属性:周长
  get perimeter(): number {
    return 2 * (this._width + this._height);
  }
  
  // 计算属性:是否为正方形
  get isSquare(): boolean {
    return this._width === this._height;
  }
  
  // 计算属性:对角线长度
  get diagonal(): number {
    return Math.sqrt(this._width * this._width + this._height * this._height);
  }
}

const rect = new Rectangle(10, 5);
console.log(rect.area);      // 50(自动计算)
console.log(rect.perimeter); // 30(自动计算)
console.log(rect.isSquare);  // false(自动计算)
console.log(rect.diagonal);  // 11.180339887498949(自动计算)

rect.width = 8;
console.log(rect.area);      // 40(自动重新计算)

高级用法

访问器与访问修饰符

访问器可以与访问修饰符(publicprivateprotected)组合使用:

typescript
class Product {
  private _price: number;
  private _discount: number = 0;
  
  constructor(price: number) {
    this._price = price;
  }
  
  // 公共 getter
  public get price(): number {
    return this._price;
  }
  
  // 公共 setter(带验证)
  public set price(value: number) {
    if (value < 0) {
      throw new Error("Price cannot be negative");
    }
    this._price = value;
  }
  
  // 私有 getter(只能在类内部使用)
  private get discount(): number {
    return this._discount;
  }
  
  // 公共方法访问私有 getter
  public getDiscountedPrice(): number {
    return this.price * (1 - this.discount);
  }
  
  public setDiscount(percentage: number): void {
    if (percentage < 0 || percentage > 1) {
      throw new Error("Discount must be between 0 and 1");
    }
    this._discount = percentage;
  }
}

const product = new Product(100);
console.log(product.price);           // 100
console.log(product.getDiscountedPrice()); // 100

product.setDiscount(0.2); // 20% 折扣
console.log(product.getDiscountedPrice()); // 80

// ❌ 错误:不能直接访问私有 getter
// console.log(product.discount); // Error: Property 'discount' is private

访问器与只读属性

如果只有 getter 而没有 setter,属性就是只读的,这与 readonly 关键字类似:

typescript
class Person {
  private _id: number;
  private _name: string;
  private _birthDate: Date;
  
  constructor(id: number, name: string, birthDate: Date) {
    this._id = id;
    this._name = name;
    this._birthDate = birthDate;
  }
  
  // 只读属性:ID
  get id(): number {
    return this._id;
  }
  
  // 可读写属性:姓名
  get name(): string {
    return this._name;
  }
  
  set name(value: string) {
    this._name = value;
  }
  
  // 只读属性:年龄(计算属性)
  get age(): number {
    const today = new Date();
    let age = today.getFullYear() - this._birthDate.getFullYear();
    const monthDiff = today.getMonth() - this._birthDate.getMonth();
    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < this._birthDate.getDate())) {
      age--;
    }
    return age;
  }
  
  // 只读属性:出生日期
  get birthDate(): Date {
    return new Date(this._birthDate); // 返回副本,防止外部修改
  }
}

const person = new Person(1, "Alice", new Date(1990, 0, 1));
console.log(person.id);        // 1
console.log(person.age);       // 自动计算的年龄
console.log(person.birthDate); // 出生日期

person.name = "Bob";           // ✅ 可以修改

// ❌ 错误:只读属性不能修改
// person.id = 2;              // Error
// person.age = 30;             // Error(只有 getter)
// person.birthDate = new Date(); // Error(只有 getter)

延迟计算(Lazy Evaluation)

Getter 可以用于实现延迟计算,只在第一次访问时才计算值:

typescript
class ExpensiveComputation {
  private _result: number | null = null;
  private _input: number;
  
  constructor(input: number) {
    this._input = input;
  }
  
  // 延迟计算:只在第一次访问时计算
  get result(): number {
    if (this._result === null) {
      console.log("Computing expensive result...");
      // 模拟耗时计算
      this._result = this._input * this._input * this._input;
    }
    return this._result;
  }
  
  // 重置计算结果
  public reset(): void {
    this._result = null;
  }
}

const computation = new ExpensiveComputation(10);
// 此时还没有计算

console.log(computation.result); // "Computing expensive result..." 然后输出 1000
console.log(computation.result); // 直接输出 1000(不再计算)

computation.reset();
console.log(computation.result); // "Computing expensive result..." 然后输出 1000

使用示例

示例 1:用户配置管理

typescript
class UserSettings {
  private _theme: "light" | "dark" = "light";
  private _language: string = "en";
  private _notifications: boolean = true;
  private _fontSize: number = 14;
  
  // 主题设置(带验证)
  get theme(): "light" | "dark" {
    return this._theme;
  }
  
  set theme(value: "light" | "dark") {
    if (value !== "light" && value !== "dark") {
      throw new Error("Theme must be 'light' or 'dark'");
    }
    this._theme = value;
    this._saveSettings(); // 自动保存
  }
  
  // 语言设置
  get language(): string {
    return this._language;
  }
  
  set language(value: string) {
    if (value.length !== 2) {
      throw new Error("Language code must be 2 characters");
    }
    this._language = value.toLowerCase();
    this._saveSettings();
  }
  
  // 通知设置
  get notifications(): boolean {
    return this._notifications;
  }
  
  set notifications(value: boolean) {
    this._notifications = value;
    this._saveSettings();
  }
  
  // 字体大小(带范围验证)
  get fontSize(): number {
    return this._fontSize;
  }
  
  set fontSize(value: number) {
    if (value < 8 || value > 72) {
      throw new Error("Font size must be between 8 and 72");
    }
    this._fontSize = value;
    this._saveSettings();
  }
  
  // 私有方法:保存设置
  private _saveSettings(): void {
    console.log("Settings saved:", {
      theme: this._theme,
      language: this._language,
      notifications: this._notifications,
      fontSize: this._fontSize
    });
    // 实际应用中会保存到 localStorage 或服务器
  }
  
  // 计算属性:设置摘要
  get summary(): string {
    return `Theme: ${this._theme}, Language: ${this._language}, Font: ${this._fontSize}px`;
  }
}

const settings = new UserSettings();
console.log(settings.theme); // "light"

settings.theme = "dark";      // "Settings saved: ..."
settings.fontSize = 16;      // "Settings saved: ..."
console.log(settings.summary); // "Theme: dark, Language: en, Font: 16px"

// ❌ 错误:无效的主题
// settings.theme = "blue"; // Error: Theme must be 'light' or 'dark'

示例 2:购物车系统

typescript
class CartItem {
  private _productId: number;
  private _productName: string;
  private _price: number;
  private _quantity: number;
  
  constructor(productId: number, productName: string, price: number, quantity: number = 1) {
    this._productId = productId;
    this._productName = productName;
    this._price = price;
    this._quantity = quantity;
  }
  
  // 只读属性:产品 ID
  get productId(): number {
    return this._productId;
  }
  
  // 只读属性:产品名称
  get productName(): string {
    return this._productName;
  }
  
  // 只读属性:单价
  get price(): number {
    return this._price;
  }
  
  // 可读写属性:数量(带验证)
  get quantity(): number {
    return this._quantity;
  }
  
  set quantity(value: number) {
    if (value < 1) {
      throw new Error("Quantity must be at least 1");
    }
    if (!Number.isInteger(value)) {
      throw new Error("Quantity must be an integer");
    }
    this._quantity = value;
  }
  
  // 计算属性:小计
  get subtotal(): number {
    return this._price * this._quantity;
  }
}

class ShoppingCart {
  private _items: CartItem[] = [];
  
  // 只读属性:购物车项目列表
  get items(): readonly CartItem[] {
    return [...this._items]; // 返回副本,防止外部修改
  }
  
  // 计算属性:总价
  get total(): number {
    return this._items.reduce((sum, item) => sum + item.subtotal, 0);
  }
  
  // 计算属性:商品总数
  get itemCount(): number {
    return this._items.reduce((sum, item) => sum + item.quantity, 0);
  }
  
  // 计算属性:是否为空
  get isEmpty(): boolean {
    return this._items.length === 0;
  }
  
  public addItem(item: CartItem): void {
    const existingItem = this._items.find(i => i.productId === item.productId);
    if (existingItem) {
      existingItem.quantity += item.quantity;
    } else {
      this._items.push(item);
    }
  }
  
  public removeItem(productId: number): boolean {
    const index = this._items.findIndex(item => item.productId === productId);
    if (index > -1) {
      this._items.splice(index, 1);
      return true;
    }
    return false;
  }
  
  public clear(): void {
    this._items = [];
  }
}

const cart = new ShoppingCart();

const item1 = new CartItem(1, "Laptop", 999, 1);
const item2 = new CartItem(2, "Mouse", 29, 2);

cart.addItem(item1);
cart.addItem(item2);

console.log(cart.total);     // 1057
console.log(cart.itemCount);  // 3
console.log(cart.isEmpty);    // false

item2.quantity = 3;          // 使用 setter
console.log(cart.total);     // 1086

示例 3:表单验证

typescript
class FormField {
  private _value: string = "";
  private _errors: string[] = [];
  private _isDirty: boolean = false;
  
  constructor(private _name: string, private _validator?: (value: string) => string[]) {
  }
  
  // 值(带验证)
  get value(): string {
    return this._value;
  }
  
  set value(newValue: string) {
    this._value = newValue;
    this._isDirty = true;
    this._validate();
  }
  
  // 只读属性:字段名
  get name(): string {
    return this._name;
  }
  
  // 只读属性:错误列表
  get errors(): readonly string[] {
    return [...this._errors];
  }
  
  // 计算属性:是否有错误
  get hasErrors(): boolean {
    return this._errors.length > 0;
  }
  
  // 计算属性:是否有效
  get isValid(): boolean {
    return !this.hasErrors && this._isDirty;
  }
  
  // 计算属性:是否已修改
  get isDirty(): boolean {
    return this._isDirty;
  }
  
  // 私有方法:验证
  private _validate(): void {
    this._errors = [];
    
    if (this._validator) {
      const validationErrors = this._validator(this._value);
      this._errors.push(...validationErrors);
    }
  }
  
  public reset(): void {
    this._value = "";
    this._errors = [];
    this._isDirty = false;
  }
}

// 邮箱验证器
function emailValidator(value: string): string[] {
  const errors: string[] = [];
  if (!value) {
    errors.push("Email is required");
  } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
    errors.push("Invalid email format");
  }
  return errors;
}

// 密码验证器
function passwordValidator(value: string): string[] {
  const errors: string[] = [];
  if (!value) {
    errors.push("Password is required");
  } else if (value.length < 8) {
    errors.push("Password must be at least 8 characters");
  } else if (!/[A-Z]/.test(value)) {
    errors.push("Password must contain at least one uppercase letter");
  } else if (!/[a-z]/.test(value)) {
    errors.push("Password must contain at least one lowercase letter");
  } else if (!/\d/.test(value)) {
    errors.push("Password must contain at least one number");
  }
  return errors;
}

const emailField = new FormField("email", emailValidator);
const passwordField = new FormField("password", passwordValidator);

emailField.value = "invalid-email";
console.log(emailField.hasErrors);  // true
console.log(emailField.errors);     // ["Invalid email format"]

emailField.value = "user@example.com";
console.log(emailField.hasErrors);  // false
console.log(emailField.isValid);    // true

passwordField.value = "weak";
console.log(passwordField.errors);  // ["Password must be at least 8 characters", ...]

passwordField.value = "StrongPass123";
console.log(passwordField.hasErrors); // false
console.log(passwordField.isValid);   // true

类型检查示例

常见错误

typescript
// ❌ 错误:Getter 不能有参数
class Example1 {
  // get value(param: string): string { // Error: A 'get' accessor cannot have parameters
  //   return param;
  // }
}

// ❌ 错误:Setter 必须有且仅有一个参数
class Example2 {
  private _value: string;
  
  // set value(): void { // Error: A 'set' accessor must have exactly one parameter
  //   this._value = "";
  // }
  
  // set value(v1: string, v2: string): void { // Error: A 'set' accessor must have exactly one parameter
  //   this._value = v1 + v2;
  // }
}

// ❌ 错误:访问器不能有返回类型注解(getter 除外)
class Example3 {
  private _value: string;
  
  get value(): string { // ✅ Getter 可以有返回类型
    return this._value;
  }
  
  // set value(v: string): void { // ❌ Setter 不能有返回类型注解
  //   this._value = v;
  // }
}

正确写法

typescript
// ✅ 正确:Getter 和 Setter 的基本用法
class User {
  private _name: string;
  
  get name(): string {
    return this._name;
  }
  
  set name(value: string) {
    this._name = value;
  }
}

const user = new User();
user.name = "Alice";
console.log(user.name); // "Alice"

// ✅ 正确:只有 Getter(只读属性)
class Config {
  private _apiKey: string = "default";
  
  get apiKey(): string {
    return this._apiKey;
  }
}

const config = new Config();
console.log(config.apiKey); // "default"
// config.apiKey = "new"; // Error: Cannot set property apiKey

// ✅ 正确:Getter 和 Setter 的类型可以不同(不推荐)
class Converter {
  private _value: number = 0;
  
  get value(): number {
    return this._value;
  }
  
  set value(v: string | number) {
    this._value = typeof v === "string" ? parseFloat(v) : v;
  }
}

const converter = new Converter();
converter.value = "123";
console.log(converter.value); // 123 (number)

注意事项

提示

  • 访问器使用起来像普通属性,但实际上是方法调用
  • Getter 和 Setter 可以分别定义,也可以只定义其中一个
  • 如果只有 getter,属性就是只读的
  • Getter 非常适合用于计算属性和延迟计算
  • Setter 非常适合用于数据验证和副作用处理
  • 访问器可以与访问修饰符(public、private、protected)组合使用

注意

  • Getter 不能有参数,Setter 必须有且仅有一个参数
  • Setter 不能有返回类型注解(void 也不可以)
  • 访问器在编译后会转换为普通方法,不会影响运行时性能
  • 如果同时定义了同名的普通属性和访问器,访问器会覆盖普通属性
  • 访问器中的 this 指向类的实例

重要

  • 访问器是 TypeScript/JavaScript 的特性,编译后的代码中会保留
  • 不要在 getter 中执行耗时操作,因为每次访问属性都会调用 getter
  • 对于需要频繁访问的计算属性,考虑使用缓存机制
  • Setter 中应该进行数据验证,确保数据的一致性
  • 访问器不能用于解构赋值:const { value } = obj; 不会调用 getter

信息

访问器的优势

  • 封装性:隐藏内部实现,提供统一的访问接口
  • 验证性:在设置值时进行数据验证
  • 计算性:动态计算属性值
  • 灵活性:可以在访问或修改时执行额外逻辑

适用场景

  • 需要数据验证的属性
  • 计算属性(基于其他属性计算)
  • 只读属性(只有 getter)
  • 需要副作用的属性访问(如自动保存、日志记录)
  • 延迟计算(Lazy Evaluation)

相关链接

基于 VitePress 构建