源本科技 | 码上会

JavaScript getter/setter

2026/01/13
15
0

学习目标

  • 理解 Getter 和 Setter 的基本语法与工作机制

  • 掌握在对象字面量和 ES6 类中定义访问器的方法

  • 能够结合私有字段实现安全的数据封装

  • 了解 Object.defineProperty() 动态创建访问器的高级用法

  • 明确 Getter/Setter 的适用场景与性能权衡


什么是 Getter 与 Setter

GetterSetter 是 ECMAScript 5(2009)引入的特殊方法,用于拦截对对象属性的读取和赋值操作。它们让属性访问看起来像普通字段,但背后可执行自定义逻辑。

  • 语法透明:使用时如同访问普通属性(obj.prop),无需调用函数

  • 逻辑隐藏:可在读写时执行验证、计算、日志等操作

  • 增强封装:配合私有字段可完全控制数据访问


基础用法

类中的 Getter 与 Setter

Getter:安全读取

class Person {
    constructor(name) {
        this._name = name; // 下划线表示“受保护”
    }

    // Getter:当访问 person.name 时自动调用
    get name() {
        return this._name;
    }
}

const person = new Person('Anjali');
console.log(person.name); // 输出: Anjali(看似读属性,实则调用方法)

Setter:受控写入

class Person {
    constructor(name) {
        this._name = name;
    }

    set name(newName) {
        if (typeof newName !== 'string' || newName.trim() === '') {
            throw new Error('Name must be a non-empty string');
        }
        this._name = newName.trim();
    }
}

const person = new Person('Anjali');
person.name = 'Ayushi'; // 触发 setter
// person.name = '';    // 抛出错误

注意:set 方法必须且只能接受一个参数(新值)


对象字面量中的访问器

不仅类支持,普通对象也可直接定义 Getter/Setter:

const user = {
    firstName: "Anurag",
    lastName: "Das",

    // Getter:组合全名
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    },

    // Setter:拆分全名
    set fullName(name) {
        const parts = name.split(" ");
        this.firstName = parts[0] || "";
        this.lastName = parts[1] || "";
    }
};

console.log(user.fullName); // "Anurag Das"
user.fullName = "Anuj Jain";
console.log(user.firstName, user.lastName); // "Anuj" "Jain"

结合私有字段实现强封装

ES2022+

使用 # 前缀声明真正的私有字段,配合 Getter/Setter 提供安全接口:

class BankAccount {
    #balance; // 私有字段,外部无法直接访问

    constructor(initialBalance) {
        this.#balance = initialBalance;
    }

    // 只读访问余额
    get balance() {
        return this.#balance;
    }

    // 受控存款
    set balance(amount) {
        if (amount < 0) {
            console.log("Balance cannot be negative!");
            return;
        }
        this.#balance = amount;
    }

    // 更合理的存款方法(推荐)
    deposit(amount) {
        if (amount > 0) {
            this.#balance += amount;
        }
    }
}

const account = new BankAccount(1000);
console.log(account.balance); // 1000
account.balance = -500;       // 输出警告,余额不变
account.deposit(200);
console.log(account.balance); // 1200

优势

  • 外部无法通过 account.#balance 访问私有字段(语法错误)

  • 所有状态变更必须经过验证逻辑


动态定义访问器

Object.defineProperty()

当需要在运行时动态添加访问器时,可使用此方法:

const employee = { name: "Anjali" };

Object.defineProperty(employee, "greeting", {
    get() {
        return `Hello, ${this.name}!`;
    },
    set(newName) {
        this.name = newName; // 更新原始字段
    },
    enumerable: true,   // 可枚举(for...in 中可见)
    configurable: false // 不可删除或重新配置
});

console.log(employee.greeting); // "Hello, Anjali!"
employee.greeting = "Ayushi";   // 触发 setter
console.log(employee.name);     // "Ayushi"

批量定义多个访问器

const obj = {};
['x', 'y'].forEach(prop => {
    let value = 0;
    Object.defineProperty(obj, prop, {
        get() { return value; },
        set(newValue) { 
            if (typeof newValue === 'number') value = newValue;
        }
    });
});

高级应用场景

场景 1:延迟计算

class DataProcessor {
    constructor(data) {
        this.rawData = data;
        this._processedData = null;
    }

    get processedData() {
        if (this._processedData === null) {
            console.log("Computing expensive operation...");
            this._processedData = this.rawData.map(x => x * 2);
        }
        return this._processedData;
    }
}

const processor = new DataProcessor([1, 2, 3]);
console.log(processor.processedData); // 首次计算并缓存
console.log(processor.processedData); // 直接返回缓存结果

场景 2:数据验证与转换

class Temperature {
    constructor(celsius) {
        this.celsius = celsius;
    }

    set celsius(value) {
        if (value < -273.15) {
            throw new Error("Temperature below absolute zero!");
        }
        this._celsius = value;
    }

    get fahrenheit() {
        return (this._celsius * 9/5) + 32;
    }
}

场景 3:调试与变更追踪

class Config {
    set debugMode(enabled) {
        console.log(`Debug mode changed to: ${enabled}`);
        this._debug = enabled;
    }

    get debugMode() {
        return this._debug;
    }
}

Getter/Setter 比较

特性

Getter/Setter

普通方法

直接属性

调用方式

obj.prop

obj.method()

obj.prop

语义清晰度

表示“这是数据”

表示“这是行为”

纯数据

封装能力

强(可隐藏逻辑)

中(需显式调用)

性能

略低(函数调用开销)

正常

最高

适用场景

验证、计算属性、私有字段代理

复杂操作、副作用

简单数据存储

经验法则

  • 若属性需要验证、计算或副作用 → 用 Getter/Setter

  • 若操作明显是行为(如 save(), calculate())→ 用普通方法

  • 若仅为简单数据容器 → 直接属性即可


最佳实践

  1. 命名一致性:Getter/Setter 名称应与概念属性名一致(如 name 而非 getName

  2. 避免过度使用:简单属性无需包装

  3. 文档说明:在复杂逻辑处添加注释

  4. 优先私有字段:在现代 JS 中用 #field 而非 _field


重点总结

  • Getter/Setter 让属性访问兼具字段的简洁性与方法的灵活性

  • 在类和对象字面量中均可定义,也支持通过 Object.defineProperty() 动态创建

  • 配合私有字段(#)可实现真正的数据封装

  • 适用于:数据验证、计算属性、懒加载、调试追踪等场景

  • 应避免在 Getter 中产生副作用,Setter 中必须包含必要验证

  • 性能敏感场景需谨慎使用,但多数应用中开销可忽略


思考题

  1. 如果一个类同时定义了 get prop() 和普通属性 prop,会发生什么?为什么?

  2. 如何使用 Getter 实现一个“只读”属性,使其在严格模式下赋值时报错?