TypeScript getter 和 setter

为什么要使用 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 实时计算出来的。

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号