阿里云主机折上折
  • 微信号
Current Site:Index > Implementation solutions for the Private Property pattern

Implementation solutions for the Private Property pattern

Author:Chuan Chen 阅读数:57006人阅读 分类: JavaScript

Implementation Solutions for the Private Property Pattern

The private property pattern is a design pattern that restricts direct access to object properties, ensuring certain properties are only visible within the object through specific technical means. JavaScript does not natively support private properties, but developers can simulate this feature in various ways.

Simulating Private Properties via Naming Conventions

The most common simulation method is distinguishing private properties through naming conventions, typically using an underscore prefix:

class User {
  constructor(name) {
    this._name = name;  // Conventionally a private property
  }

  getName() {
    return this._name;
  }
}

const user = new User('John');
console.log(user._name);  // Still accessible but violates the convention

This approach relies solely on coding standards and does not enforce true privacy. Its advantage lies in simplicity, making it suitable for team collaboration as a code convention.

True Privacy via Closures

Leveraging closures can create genuinely private variables:

function Car() {
  // Truly private variable
  let speed = 0;

  this.accelerate = function() {
    speed += 10;
    console.log(`Current speed: ${speed}km/h`);
  };

  this.getSpeed = function() {
    return speed;
  };
}

const myCar = new Car();
myCar.accelerate();  // Current speed: 10km/h
console.log(myCar.speed);  // undefined

The drawback is that each instance creates a copy of the methods, resulting in lower memory efficiency. This method is suitable for scenarios with few private variables and limited instances.

Storing Private Data with WeakMap

ES6's WeakMap offers a more elegant way to implement private properties:

const privateData = new WeakMap();

class BankAccount {
  constructor(balance) {
    privateData.set(this, { balance });
  }

  deposit(amount) {
    const data = privateData.get(this);
    data.balance += amount;
  }

  getBalance() {
    return privateData.get(this).balance;
  }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance());  // 1500
console.log(account.balance);      // undefined

WeakMap stores private data using instances as keys, preventing external access. This method does not interfere with garbage collection and is suitable for large-scale applications.

Using Symbols as Property Keys

ES6 Symbols can also create pseudo-private properties:

const _radius = Symbol('radius');

class Circle {
  constructor(radius) {
    this[_radius] = radius;
  }

  getRadius() {
    return this[_radius];
  }
}

const circle = new Circle(5);
console.log(circle.getRadius());  // 5
console.log(circle[_radius]);     // Still accessible if the Symbol reference is held

While safer than naming conventions, Symbol properties can still be retrieved via Object.getOwnPropertySymbols().

ES2019 Private Fields

The latest specification finally introduces true private field syntax:

class Person {
  #age;  // Private field declaration

  constructor(name, age) {
    this.name = name;
    this.#age = age;
  }

  #privateMethod() {
    return `Age: ${this.#age}`;
  }

  getInfo() {
    return `${this.name}, ${this.#privateMethod()}`;
  }
}

const person = new Person('Alice', 30);
console.log(person.getInfo());  // Alice, Age: 30
console.log(person.#age);      // SyntaxError

Private fields are identified with a # prefix and represent true language-level privacy. Currently requires modern browser or transpiler support.

Module Pattern for Privacy

Using IIFE and closures to create private spaces:

const counter = (function() {
  let count = 0;

  return {
    increment() {
      count++;
    },
    get() {
      return count;
    }
  };
})();

counter.increment();
console.log(counter.get());  // 1
console.log(counter.count);  // undefined

This pattern is ideal for creating singleton objects, with private variables residing in closures and completely inaccessible from outside.

Controlling Property Access with Proxy

Proxies allow fine-grained control over property access:

const createPrivateObject = (obj, privateProps) => {
  return new Proxy(obj, {
    get(target, prop) {
      if (privateProps.includes(prop)) {
        throw new Error(`Cannot access private property ${prop}`);
      }
      return target[prop];
    },
    set(target, prop, value) {
      if (privateProps.includes(prop)) {
        throw new Error(`Cannot set private property ${prop}`);
      }
      target[prop] = value;
      return true;
    }
  });
};

const user = createPrivateObject({
  name: 'Bob',
  _password: '123456'
}, ['_password']);

console.log(user.name);      // Bob
console.log(user._password); // Error

Proxies offer flexibility but come with performance overhead, making them suitable for dynamic permission control scenarios.

Comparative Analysis of Solutions

Naming Convention: Simple but unenforced
Closures: Truly private but memory-inefficient
WeakMap: Balanced but syntactically complex
Symbol: Safer than naming conventions
Private Fields: Future standard solution
Module Pattern: Ideal for singletons
Proxy: Most flexible but least performant

Consider the following when choosing a solution:

  • Need for true privacy
  • Performance requirements
  • Code maintainability
  • Runtime environment support

Practical Application Examples

1. Game Character State Management

class Character {
  #health = 100;
  #inventory = new Set();

  takeDamage(amount) {
    this.#health = Math.max(0, this.#health - amount);
    if (this.#health === 0) {
      this.#die();
    }
  }

  #die() {
    console.log('Character died');
  }

  addItem(item) {
    this.#inventory.add(item);
  }
}

2. Form Validator

const createValidator = () => {
  const rules = new Map();

  return {
    addRule(field, ruleFn) {
      if (!rules.has(field)) {
        rules.set(field, []);
      }
      rules.get(field).push(ruleFn);
    },
    validate(formData) {
      return Object.entries(formData).every(([field, value]) => {
        const fieldRules = rules.get(field) || [];
        return fieldRules.every(rule => rule(value));
      });
    }
  };
};

3. Cache System Implementation

class DataCache {
  #cache = new Map();
  #maxSize = 100;

  get(key) {
    return this.#cache.get(key);
  }

  set(key, value) {
    if (this.#cache.size >= this.#maxSize) {
      const firstKey = this.#cache.keys().next().value;
      this.#cache.delete(firstKey);
    }
    this.#cache.set(key, value);
  }

  clear() {
    this.#cache.clear();
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.