getter/setter 是什么?如何使用它们?
下文默认基于 TypeScript 5.x 与现代 JavaScript(ES2023+)语境。
getter/setter本质是 JavaScript 运行时特性,TypeScript 只是在此基础上补充了类型检查。
面试速答(30 秒版 TL;DR)
getter和setter是对象属性的访问器(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 = x | obj.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使用,避免递归。