static class fields and static private methods
ECMAScript 12 Static Class Fields
Static class fields allow defining properties directly on the class itself without going through the constructor. These properties belong to the class, not its instances. The syntax uses the static
keyword for declaration:
class Counter {
static initial = 0; // Static public field
static #max = 100; // Static private field (new in ES12)
static getMax() {
return this.#max;
}
}
console.log(Counter.initial); // 0
console.log(Counter.getMax()); // 100
Key differences between static fields and instance fields:
- Access method: Accessed directly via the class name
- Memory allocation: Initialized only once when the class is loaded
- Inheritance behavior: Subclasses inherit static fields from the parent class
Typical use cases include:
- Class-level configurations (e.g., default values, constants)
- State shared across instances
- Collections of utility functions
Static Private Methods and Fields
ES12 introduced true class private members, including static private methods and fields, using the #
prefix:
class Logger {
static #logLevel = 'INFO'; // Static private field
static #validateLevel(level) { // Static private method
return ['DEBUG', 'INFO', 'WARN', 'ERROR'].includes(level);
}
static setLevel(level) {
if (this.#validateLevel(level)) {
this.#logLevel = level;
}
}
static log(message) {
console.log(`[${this.#logLevel}] ${message}`);
}
}
Logger.setLevel('DEBUG');
Logger.log('Test message'); // [DEBUG] Test message
console.log(Logger.#logLevel); // SyntaxError
Characteristics of private members:
- Externally inaccessible, including by subclasses
- Must be declared in advance
- Hard private: Cannot be accessed via reflection
Comparison with TypeScript
TypeScript's private
modifier is a compile-time constraint, whereas ES12's #
private is enforced at runtime:
// TypeScript
class TSExample {
private static secret = 123;
}
console.log((TSExample as any).secret); // 123 (still accessible at runtime)
// ECMAScript
class JSExample {
static #secret = 123;
}
console.log(JSExample.#secret); // SyntaxError (completely inaccessible)
Practical Example
Implementing an API client with caching:
class ApiClient {
static #cache = new Map();
static #defaultTimeout = 5000;
static async fetch(url) {
if (this.#cache.has(url)) {
return this.#cache.get(url);
}
const controller = new AbortController();
const timeoutId = setTimeout(
() => controller.abort(),
this.#defaultTimeout
);
try {
const response = await fetch(url, { signal: controller.signal });
const data = await response.json();
this.#cache.set(url, data);
return data;
} finally {
clearTimeout(timeoutId);
}
}
static clearCache() {
this.#cache.clear();
}
}
// Usage example
await ApiClient.fetch('https://api.example.com/data');
Performance Considerations
Advantages of static members over instance members:
- Memory efficiency: Avoids redundant creation per instance
- Access speed: No prototype chain lookup required
However, note:
- Overuse may bloat the class
- Long-lived static fields can cause memory leaks
Compatibility and Transpilation
For older environments, Babel transpiles static private members using WeakMap:
// Before transpilation
class Original {
static #privateField = 42;
}
// After transpilation
var _privateField = new WeakMap();
class Original {
constructor() {
_privateField.set(this, 42);
}
}
Key differences:
- Transpiled "private" fields can still be bypassed via WeakMap
- Static fields become instance fields
- Performance characteristics differ significantly
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn