跳到主要内容

getter/setter 是什么?如何使用它们?

下文默认基于 TypeScript 5.x 与现代 JavaScript(ES2023+)语境。getter/setter 本质是 JavaScript 运行时特性,TypeScript 只是在此基础上补充了类型检查。

面试速答(30 秒版 TL;DR)

  • gettersetter 是对象属性的访问器(accessor)。
  • getter 负责“读取属性时执行什么逻辑”,setter 负责“给属性赋值时执行什么逻辑”。
  • 它们的核心价值不是语法花样,而是:
    • 封装内部状态;
    • 在读写时做校验、转换、派生计算;
    • 保持“像普通属性一样用”,但背后能挂逻辑。
  • 常见写法有两类:
    • 类里用 get / set
    • 对普通对象用 Object.defineProperty
  • 面试里最好补一句:getter/setter属性访问拦截,不是普通方法;obj.x 看起来像读字段,实际上可能在执行函数逻辑。

心智模型:它是“属性语法 + 函数逻辑”

所以它和普通字段的区别是:

  • 普通字段:直接存值;
  • 访问器属性:表面上是字段,底层实际上带有执行逻辑。

1. 什么是 getter

getter 是“读取属性时自动调用的函数”。

class User {
private firstName: string;
private lastName: string;

constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}

get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}

const user = new User("Terry", "Lan");
console.log(user.fullName); // Terry Lan

注意这里调用方式不是 user.fullName(),而是 user.fullName

也就是说:

  • 语法上像字段;
  • 行为上像执行了一段函数。

适合 getter 的场景

  • 根据多个内部字段计算派生值;
  • 对外隐藏底层存储结构;
  • 读取时做轻量格式化;
  • 给旧 API 做兼容层。

2. 什么是 setter

setter 是“给属性赋值时自动调用的函数”。

class User {
private _age = 0;

get age() {
return this._age;
}

set age(value: number) {
if (value < 0) {
throw new Error("age 不能小于 0");
}
this._age = value;
}
}

const user = new User();
user.age = 18;
console.log(user.age); // 18

它最常见的价值是:

  • 写入前做参数校验;
  • 把外部输入转换成内部统一格式;
  • 阻止非法状态进入对象。

3. 在 TypeScript 里怎么写

3.1 类中的 get / set

这是最常见、最适合面试回答的形式。

class Person {
private _name = "";

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

set name(value: string) {
this._name = value.trim();
}
}

这里要注意几个点:

  • getter 没有参数;
  • setter 只能有一个参数;
  • 对外访问时不用加括号;
  • 真正存值时通常会配合一个私有字段,比如 _name

3.2 普通对象上的访问器属性

除了类,也可以直接给对象定义:

const person = {
firstName: "Terry",
lastName: "Lan",
};

Object.defineProperty(person, "fullName", {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(value: string) {
const [firstName, lastName] = value.split(" ");
this.firstName = firstName ?? "";
this.lastName = lastName ?? "";
},
});

console.log(person.fullName);
person.fullName = "Ada Lovelace";

这类写法更偏底层,常见于:

  • 框架内部;
  • 元编程;
  • 兼容旧对象结构;
  • 手动定义属性描述符。

4. getter/setter 的典型用途

4.1 封装内部状态

你不希望外部直接改内部字段,而是希望所有写入都经过规则检查。

class Temperature {
private _celsius = 0;

get celsius() {
return this._celsius;
}

set celsius(value: number) {
if (value < -273.15) {
throw new Error("低于绝对零度");
}
this._celsius = value;
}
}

4.2 提供派生属性

class Rectangle {
constructor(
public width: number,
public height: number,
) {}

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

这种场景很适合 getter,因为它表达的是“根据已有状态算出来的值”。

4.3 做输入格式标准化

class Account {
private _email = "";

get email() {
return this._email;
}

set email(value: string) {
this._email = value.trim().toLowerCase();
}
}

5. getter/setter 和普通方法有什么区别

这是面试很爱追问的一点。

对比项getter/setter普通方法
调用方式obj.value / obj.value = xobj.getValue()
语义像属性像动作
是否适合复杂副作用不适合更适合
典型用途派生值、校验、封装显式业务行为

实际建议是:

  • “这个值像属性”时,用 getter/setter
  • “这是一个动作”时,用方法更清楚。

比如:

  • user.fullName 适合做 getter;
  • user.refreshProfile() 就明显应该是方法。

6. TypeScript 里要注意的点

6.1 它不改变运行时本质

TypeScript 不会发明新的访问器机制,它只是让你能写:

get age(): number
set age(value: number)

真正跑起来时,仍然是 JavaScript 的访问器属性语义。

6.2 通常会配合私有字段使用

如果你在 set name 里写 this.name = value,会再次触发 setter,自我递归。

错误示例:

class BadUser {
set name(value: string) {
this.name = value;
}
}

这会导致无限递归,最终栈溢出。

所以常见模式都是:

  • 对外暴露 name
  • 对内实际存 _name#name

6.3 getter 不应该太重

因为调用者看到的是 obj.value,通常会默认它是一个轻量读取。

如果你在 getter 里:

  • 发网络请求;
  • 做很重的循环;
  • 改外部状态;

会让代码语义非常别扭。

稳妥原则是:

  • getter 尽量纯、尽量轻;
  • 重逻辑放到显式方法里。

6.4 只读不一定等于真正不可改

如果只有 getter 没有 setter:

class User {
private _id = "u1";

get id() {
return this._id;
}
}

这意味着外部不能通过 user.id = ... 改它,但类内部仍然可以改 _id

所以它是“受控访问”,不是“运行时绝对不可变”。


7. 高频追问

7.1 getter/setter 和 readonly 有什么区别

readonly 更偏类型约束,限制“这个属性不能被重新赋值”;
getter/setter 更偏访问控制机制,你可以自定义读写逻辑。

简单理解:

  • readonly:不让你乱改;
  • getter/setter:让你按规则读写。

7.2 getter 可以有副作用吗

技术上可以,但工程上通常不建议。

因为属性读取一般被理解为“拿值”,不是“顺手干一堆事”。

7.3 setter 能返回值吗

不能按普通函数那样把返回值交给赋值表达式使用。

obj.x = 1 的重点是写入行为,不是拿 setter 的返回结果。

7.4 访问器和方法怎么选

如果某个成员表达的是“对象当前的一个属性视图”,选访问器更自然;
如果它表达的是“执行一个操作”,方法更清晰。


8. 易错点 / 坑

  • 在 setter 里给同名属性再次赋值,导致递归调用。
  • 在 getter 里塞重计算或副作用,调用者难以预期成本。
  • 误以为 getter/setter 是 TypeScript 独有特性,实际上它来自 JavaScript。
  • 把明显是动作的逻辑做成 getter,导致语义混乱。

速记要点

  • getter:读属性时触发,适合派生值和封装读取逻辑。
  • setter:写属性时触发,适合校验、转换、统一入口。
  • 外面看起来像字段,里面本质是函数。
  • TypeScript 只补类型,不改变 JS 访问器机制。
  • 实战里常配合私有字段 _x#x 使用,避免递归。