Skip to content

私有与受保护的属性和方法

私有属性和方法

1. 私有字段 (ES2022)

javascript
class BankAccount {
    // 私有字段以 # 开头
    #balance = 0;
    #accountNumber;

    constructor(accountNumber, initialBalance) {
        this.#accountNumber = accountNumber;
        if (initialBalance > 0) {
            this.deposit(initialBalance);
        }
    }

    deposit(amount) {
        if (amount > 0) {
            this.#balance += amount;
            return true;
        }
        return false;
    }

    withdraw(amount) {
        if (amount > 0 && this.#balance >= amount) {
            this.#balance -= amount;
            return true;
        }
        return false;
    }

    getBalance() {
        return this.#balance;
    }

    // 私有方法
    #validateTransaction(amount) {
        return amount > 0 && this.#balance >= amount;
    }
}

const account = new BankAccount('123456', 1000);
console.log(account.getBalance()); // 1000
// console.log(account.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class

2. 私有静态字段和方法

javascript
class PaymentProcessor {
    // 私有静态字段
    static #secretKey = 'sk_test_abcdefghijklmn';
    
    // 私有静态方法
    static #hashCredentials(data) {
        // 哈希实现...
        return data + '_hashed';
    }
    
    static processPayment(amount, cardDetails) {
        const hashedDetails = this.#hashCredentials(cardDetails);
        // 使用私有密钥发送请求
        console.log(`Processing $${amount} with key ${this.#secretKey}`);
        return true;
    }
}

PaymentProcessor.processPayment(100, {cardNumber: '4242424242424242'});
// console.log(PaymentProcessor.#secretKey); // SyntaxError: Private field '#secretKey' must be declared in an enclosing class

3. 私有字段检测

javascript
class Circle {
    #radius;

    constructor(radius) {
        this.#radius = radius;
    }

    hasRadius() {
        return #radius in this;
    }
}

const circle = new Circle(5);
console.log(circle.hasRadius()); // true

模拟受保护的属性和方法

JavaScript 没有原生支持受保护(protected)的成员,但可以通过约定或 Symbol 模拟。

1. 使用命名约定

javascript
class Person {
    constructor(name) {
        // 约定以 _ 开头的属性为受保护的
        this._name = name;
    }

    // 受保护的方法
    _validateName(name) {
        return name && name.length > 0;
    }

    setName(name) {
        if (this._validateName(name)) {
            this._name = name;
        }
    }

    getName() {
        return this._name;
    }
}

class Employee extends Person {
    constructor(name, position) {
        super(name);
        this.position = position;
    }

    introduce() {
        // 子类可以访问受保护的属性
        console.log(`Hello, I'm ${this._name} and I work as a ${this.position}`);
    }
}

const employee = new Employee('John', 'Developer');
employee.introduce(); // 'Hello, I'm John and I work as a Developer'
console.log(employee._name); // 'John' - 技术上仍可访问,但按约定不应直接访问

2. 使用 Symbol 键

javascript
// 创建一个 Symbol 键
const _name = Symbol('name');
const _validate = Symbol('validate');

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

    [_validate](name) {
        return name && name.length > 0;
    }

    setName(name) {
        if (this[_validate](name)) {
            this[_name] = name;
        }
    }

    getName() {
        return this[_name];
    }
}

class Employee extends Person {
    constructor(name, position) {
        super(name);
        this.position = position;
    }

    introduce() {
        console.log(`Hello, I'm ${this[_name]} and I work as a ${this.position}`);
    }
}

const employee = new Employee('John', 'Developer');
employee.introduce(); // 'Hello, I'm John and I work as a Developer'
console.log(employee[_name]); // undefined,除非有 _name Symbol 的引用

3. 使用 WeakMap 模拟私有属性

javascript
// 在类外创建 WeakMap 存储私有数据
const privateProps = new WeakMap();

class Person {
    constructor(name) {
        // 存储私有数据
        privateProps.set(this, { name });
    }

    getName() {
        return privateProps.get(this).name;
    }

    setName(name) {
        if (name && name.length > 0) {
            privateProps.get(this).name = name;
        }
    }
}

const person = new Person('John');
console.log(person.getName()); // 'John'
console.log(privateProps.get(person)); // { name: 'John' }

最佳实践

  1. 使用私有字段 (#) 实现真正的封装
  2. 遵循命名约定 (_) 表示受保护的成员
  3. 使用 getter 和 setter 控制属性访问
  4. 避免直接访问其他类的私有或受保护成员
  5. 了解私有字段的限制(如只能在声明的类内部访问)
  6. 使用 Symbol 或 WeakMap 增强封装性
  7. 使用私有静态字段存储敏感信息
  8. 记住 JavaScript 的封装更多是一种约定而非强制措施