阿里云主机折上折
  • 微信号
Current Site:Index > Private class methods and accessors

Private class methods and accessors

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

Definition and Syntax of Private Class Methods

ECMAScript 12 introduced private class methods and accessors, declared by adding a # prefix before the method or accessor name. This syntax ensures that class members can only be accessed within the class and cannot be directly called or modified externally. The definition of private methods is similar to that of regular methods, but requires a # symbol before the method name.

class Counter {
  #count = 0;

  #increment() {
    this.#count++;
  }

  get value() {
    return this.#count;
  }

  tick() {
    this.#increment();
  }
}

const counter = new Counter();
counter.tick();
console.log(counter.value); // 1
console.log(counter.#increment); // Error: Private member inaccessible

Implementation of Private Accessors

Private accessors include private getters and setters, which are also declared using the # prefix. Private accessors allow fine-grained control over read and write operations for private fields.

class User {
  #name;
  #age;

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

  get #userInfo() {
    return `${this.#name}, ${this.#age} years old`;
  }

  set #userInfo(value) {
    [this.#name, this.#age] = value.split(', ');
  }

  printInfo() {
    console.log(this.#userInfo);
  }

  updateInfo(info) {
    this.#userInfo = info;
  }
}

const user = new User('Zhang San', 25);
user.printInfo(); // "Zhang San, 25 years old"
user.updateInfo('Li Si, 30');
user.printInfo(); // "Li Si, 30 years old"

Private Static Methods and Accessors

Private static methods and accessors belong to the class itself rather than instances and are also declared with the # prefix. These members can only be accessed within the class via the class name and cannot be directly called by instances.

class Logger {
  static #logLevel = 'INFO';

  static #validateLevel(level) {
    return ['DEBUG', 'INFO', 'WARN', 'ERROR'].includes(level);
  }

  static get #level() {
    return this.#logLevel;
  }

  static set #level(value) {
    if (this.#validateLevel(value)) {
      this.#logLevel = value;
    }
  }

  static setLevel(level) {
    this.#level = level;
  }

  static log(message) {
    console.log(`[${this.#level}] ${message}`);
  }
}

Logger.setLevel('DEBUG');
Logger.log('Debug message'); // "[DEBUG] Debug message"

Relationship Between Private Methods and Inheritance

Private methods are not inherited by subclasses, meaning subclasses cannot access or override private methods from the parent class. This design ensures true privacy for private members.

class Parent {
  #privateMethod() {
    console.log('Parent private method');
  }

  callPrivate() {
    this.#privateMethod();
  }
}

class Child extends Parent {
  #privateMethod() {
    console.log('Child private method');
  }

  test() {
    // this.#privateMethod(); // Error: Cannot access parent private method
    this.callPrivate(); // Calls parent method
  }
}

const child = new Child();
child.test(); // "Parent private method"

Type Checking for Private Members

Since private members are truly private, external code cannot check for their existence using conventional methods. Indirect detection can be done using try-catch or the in operator.

class Example {
  #secret = 42;

  static hasSecret(obj) {
    try {
      obj.#secret;
      return true;
    } catch {
      return false;
    }
  }
}

const example = new Example();
console.log(Example.hasSecret(example)); // true
console.log(Example.hasSecret({})); // false

Comparison Between Private Members and WeakMap

Before ES12, developers often used WeakMap to simulate private members. The native private syntax is now more concise and efficient, though the two approaches differ in principle.

// WeakMap implementation
const _data = new WeakMap();
class OldWay {
  constructor(value) {
    _data.set(this, value);
  }

  getData() {
    return _data.get(this);
  }
}

// ES12 private fields
class NewWay {
  #data;
  constructor(value) {
    this.#data = value;
  }

  getData() {
    return this.#data;
  }
}

Edge Cases for Private Methods

Private methods exhibit some special behaviors. For example, attempting to "steal" a private method by assigning it to another variable outside the class is ineffective, as the private method binding still checks the original calling context.

class Secure {
  #method() {
    console.log('Secure method');
  }

  leakMethod() {
    return this.#method;
  }
}

const secure = new Secure();
const stolenMethod = secure.leakMethod();
stolenMethod(); // Error: Private method must be called via class instance

Private Members and Proxy Objects

Proxy cannot directly intercept access to private members because private member access is handled specially at the language level, bypassing the proxy mechanism.

class Target {
  #field = 'Private data';

  getField() {
    return this.#field;
  }
}

const handler = {
  get(target, prop) {
    console.log(`Accessing property: ${prop}`);
    return target[prop];
  }
};

const proxy = new Proxy(new Target(), handler);
proxy.getField(); // Only logs "Accessing property: getField," does not intercept private field access

Debugging Private Members and Developer Tools

Modern browser developer tools display private members with special notation, typically showing the # symbol or placing them in a separate category. While debugging, private member values can be viewed directly, but private methods cannot be called directly in the console.

class DebugExample {
  #debugValue = 'Debug info';
  publicValue = 'Public info';

  #debugMethod() {
    console.log(this.#debugValue);
  }

  triggerDebug() {
    this.#debugMethod();
  }
}

const debugObj = new DebugExample();
// In developer tools, inspect debugObj to see special notation for private members

Private Members and Class Expressions

Private members also work with class expressions, following the same syntax rules as class declarations. Both anonymous and named classes can include private members.

const Person = class {
  #name;

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

  get name() {
    return this.#name;
  }
};

const person = new Person('Wang Wu');
console.log(person.name); // "Wang Wu"

Potential Interaction Between Private Members and Decorators

Although the decorator proposal is still evolving, future decorators may interact specially with private members. Currently, decorators cannot be directly applied to private members.

function logged(target, name, descriptor) {
  const original = descriptor.value;
  descriptor.value = function(...args) {
    console.log(`Calling method: ${name}`);
    return original.apply(this, args);
  };
}

class Decorated {
  @logged
  publicMethod() {}

  // @logged // Currently not supported for private methods
  #privateMethod() {}
}

Relationship Between Private Members and the Class Fields Proposal

Private fields and methods are part of the class fields proposal and share the same # prefix syntax. Private fields must be declared before use, unlike public fields.

class PrivateFields {
  #mustDeclareFirst; // Must be declared first

  constructor() {
    this.#mustDeclareFirst = true;
    // this.#undeclared = false; // Error: Undeclared private field
  }
}

Difference Between Private Members and TypeScript's Private Modifier

TypeScript's private modifier is only a compile-time constraint and becomes a regular public member after compilation. ES12 private members are truly private at runtime.

// TypeScript code
class TSClass {
  private tsPrivate = 1;
  #esPrivate = 2;
}

// Compiled JavaScript
class TSClass {
  constructor() {
    this.tsPrivate = 1; // Becomes a public field after compilation
    this.#esPrivate = 2;
  }
}

Memory Management Considerations for Private Members

Private members do not increase the number of public properties on class instances, potentially offering slight memory optimization. When an instance is garbage-collected, its private members are also automatically reclaimed.

class MemoryExample {
  #largeArray = new Array(1000000).fill(0);
  publicArray = new Array(1000000).fill(0);
}

let example = new MemoryExample();
example = null; // Both arrays are reclaimed; private fields do not form special references

Relationship Between Private Members and Object Literals

Currently, private member syntax is only available for classes. Ordinary object literals do not support private property or method definitions, marking an important distinction between classes and object literals.

const obj = {
  // #privateProp: 42, // Syntax error
  publicProp: 42
};

Naming Conventions and Style Guidelines for Private Members

While the # prefix is mandatory, the community may adopt different styles for the rest of the private member name, such as lowercase, underscore-separated, or camelCase.

class NamingStyles {
  #lowercase = 1;
  #under_score = 2;
  #camelCase = 3;
  #PascalCase = 4; // Not recommended, easily confused with constructors
}

Challenges of Private Members in Unit Testing

Since private members cannot be accessed externally, testing them requires special techniques. Common approaches include testing indirectly via public methods or using a ._ prefix to denote "protected" members that tests need to access.

class Testable {
  #internalLogic(value) {
    return value * 2;
  }

  // Provides a testing entry point
  _testInternalLogic(value) {
    return this.#internalLogic(value);
  }

  publicApi(value) {
    return this.#internalLogic(value) + 10;
  }
}

Impact of Private Members on Serialization

JSON.stringify and other serialization methods ignore private members because they are not enumerable properties of the object. This may lead to unexpected data loss.

class Serializable {
  #id = 123;
  name = 'Example';

  toJSON() {
    return {
      name: this.name,
      id: this.#id // Must be explicitly included
    };
  }
}

const obj = new Serializable();
console.log(JSON.stringify(obj)); // {"name":"Example"} (without toJSON, id is lost)

Performance Considerations for Private Members

Access to private members is as performant as access to public members, as modern JavaScript engines have optimized them. Heavy use of private members does not introduce significant overhead.

class PerformanceTest {
  #count = 0;
  publicCount = 0;

  #incrementPrivate() {
    this.#count++;
  }

  incrementPublic() {
    this.publicCount++;
  }
}

const test = new PerformanceTest();
// Both increment operations have negligible performance differences in modern engines

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

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

上一篇:FinalizationRegistry

下一篇:私有类字段

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 ☕.