为什么要使用 getter 和 setter?
在上一节中,我们学习了如何使用 private 修饰符来将类的核心属性(比如银行账户的余额)变成私有财产。
但这带来了一个新的问题:外部对象确实不能随意篡改余额了,但如果用户只是想在前端页面上展示一下当前的余额,此时应该怎么办呢?private 属性在外部可是连看都不让看的。
为了解决 “私有属性无法与外部安全通信” 的问题,传统的面向对象语言(比如 Java)通常会写两个普通的方法:getBalance() 和 setBalance()。这种做法虽然安全,但每次调用都要加上括号,写起来非常繁琐。TypeScript 为我们提供了一种非常优雅的解决方案:getter 和 setter(存取器)。
getter 和 setter 这两者的本质,就是 “拦截”。它允许我们在外部像访问普通属性一样去读写数据,但在底层其实是悄悄执行了我们自定义的函数逻辑。
需要注意的是,如果我们要为某个属性(比如 price)定义存取器,那么类里面真正的那个私有属性,通常会加上一个下划线前缀(写成 _price)。之所以这样命名,主要是为了防止存取器的名字和私有属性的名字发生冲突。
注意: 在 TypeScript 中使用 getter 和 setter 有一个硬性前提:你的编译目标(target)必须是 ES5 或更高版本。如果在编译时遇到了类似 “Accessors are only available when targeting ECMAScript 5 and higher” 的报错,请检查你的 tsconfig.json 文件,确保将 "target": "es5"(或更高)配置正确。
TypeScript getter(读取器)
在 TypeScript 中,如果我们希望只允许外部读取私有属性,但不允许修改私有属性,此时可以使用 get 关键字来定义一个读取器。
示例 1:定义 getter
class User {
// 定义私有属性(以 “_” 开头)
private _name: string;
constructor(name: string) {
this._name = name;
}
// 定义 getter(读取器)
get name(): string {
console.log("[日志]: 外部读取了 name 属性");
return this._name;
}
}
const currentUser = new User("Jack");
console.log(currentUser.name);运行结果如下。
[日志]: 外部读取了 name 属性
Jack分析:
在外部调用时,我们写的是 currentUser.name,看起来就像是在直接访问一个公有属性。但实际上,TypeScript 自动拦截了这次读取操作,并执行了 get name() 函数内部的逻辑。由于我们只写了 get 没有写 set,所以此时 name 对外部来说是 “只读” 的。
TypeScript setter(设置器)
如果我们允许外部修改私有属性,并且希望在修改之前进行 “业务校验”,此时就需要使用 set 关键字来定义一个设置器。
示例 2:定义 setter 拦截非法数据
class Product {
private _price: number;
constructor(price: number) {
this._price = price;
}
// 定义 getter
get price(): number {
return this._price;
}
// 定义 setter
set price(newPrice: number) {
if (newPrice < 0) {
console.log("[警告]: 商品价格不能为负数!");
return; // 拦截赋值
}
console.log("[日志]: 价格修改成功!");
this._price = newPrice;
}
}
const item = new Product(100);
// 正常修改:触发 setter 的成功逻辑
item.price = 666;
console.log("当前价格:", item.price);
// 异常修改:触发 setter 的拦截逻辑
item.price = -666;
console.log("当前价格:", item.price);运行结果如下。
[日志]: 价格修改成功!
当前价格:666
[警告]: 商品价格不能为负数!
当前价格:666分析:
在这个例子中,当外部执行 item.price = -666; 时,等号右边的 -666 会作为参数传给 set price(newPrice)。而在 setter 内部,我们的 if 判断立刻生效,直接拦截了这次非法的负数赋值。这样下来,就可以完美实现了 “既允许外部修改、又保证了内部数据的绝对安全” 的需求了。
如果小伙伴们有 Vue 的开发经验,可能已经发现了:TypeScript 里的 getter 和 setter 思想,和 Vue 中的 computed(计算属性)是如出一辙的。
在 Vue 中,我们可以为一个计算属性提供 get() 和 set() 方法,从而使得在获取和修改响应式数据时,进行逻辑上的拦截与处理。
提示: 很多前端框架底层的响应式原理(比如 Object.defineProperty 或 ES6 的 Proxy),都会用到对数据读取和赋值的 “拦截(getter 或 setter)”。
进阶:使用 getter 实现 “计算属性”
在 TypeScript 中,getter 除了可以实现简单的属性拦截之外,它还可以高效处理 “逻辑关联数据”。
示例 3:定义计算属性
class User {
constructor(public firstName: string, public lastName: string) {}
// 定义一个计算属性 fullName
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
const user = new User("Jack", "Mo");
// 像访问普通属性一样,直接获取组合后的名字
console.log(user.fullName);运行结果如下。
Jack Mo分析:
在这个例子中,fullName 并不对应任何一个私有变量。它是根据 firstName 和 lastName 实时计算出来的。
