Public class fields are a standardized JavaScript feature that lets you declare properties directly on a class without assigning them in the constructor. Part of the ES2022 specification, public fields provide a cleaner, more declarative way to define instance and static properties. This guide covers everything you need to know to use public fields effectively in modern JavaScript applications.
By the end of this guide, you'll understand the complete syntax for instance and static fields, how initialization timing affects your code, the semantic differences between field declarations and constructor assignments, and practical patterns for React components, configuration classes, and framework integration. Whether you're maintaining legacy code or building new applications with React or Node.js, public fields provide a clean and efficient approach to defining class properties that aligns with modern JavaScript best practices. For a deeper understanding of React's internal architecture, explore our guide on React Fiber. Our web development team regularly applies these patterns when architecting maintainable class-based applications.
Public Class Fields at a Glance
ES2022
Standard Version
All Modern
Browser Support
2
Field Types
4
Key Characteristics
What Are Public Class Fields?
Public class fields are a JavaScript language feature that allows you to declare properties directly in the class body rather than assigning them inside the constructor. This syntactic sugar provides several benefits: cleaner code, better self-documentation, and alignment with class syntax from other object-oriented languages like Java and C++.
The feature was finalized as part of ECMAScript 2022 and has broad browser support, making it safe to use in production applications. Public fields are writable, enumerable, and configurable properties that exist on every instance of a class (for instance fields) or on the class constructor itself (for static fields) as defined in the MDN documentation on public class fields. For TypeScript developers, this syntax integrates seamlessly with Express and TypeScript patterns for building type-safe applications.
Key Characteristics
- Declarative Syntax: Declare properties directly in the class body
- Per-Instance or Per-Class: Instance fields exist on each object, static fields on the class itself
- Predictable Initialization: Initialized when the class is defined or instance is created
- ES2022 Standard: Universal browser support including Chrome, Firefox, Safari, Edge, and Node.js 12+
Browser and Environment Support
Public class fields are part of the Baseline initiative, indicating they work across all modern browsers and JavaScript environments without requiring transpilation or polyfills.
// Simple example of a public class field
class Rectangle {
height = 0;
width = 0;
constructor(height, width) {
this.height = height;
this.width = width;
}
}
const rect = new Rectangle(10, 20);
console.log(rect.height); // 10
console.log(rect.width); // 20
Syntax and Declaration
When building full-stack JavaScript applications, proper class structure is essential for maintainability.
JavaScript provides multiple ways to declare public class fields, each suited to different use cases. Understanding these variations helps you write clean, expressive code.
Basic Instance Fields
The simplest form of a public class field is a field declaration without an initializer:
class Counter {
count; // Declares a field, initialized to undefined
}
const counter = new Counter();
console.log(counter.count); // undefined
Instance Fields with Initializers
Most of the time, you'll want to provide an initial value. The initializer expression is evaluated each time a new instance is created:
class User {
name = 'Anonymous';
role = 'user';
isActive = true;
}
const user = new User();
console.log(user.name); // 'Anonymous'
console.log(user.role); // 'user'
Importantly, each instance gets its own independent value, which is essential for mutable types like arrays and objects:
class Container {
items = []; // New array for each instance
}
const a = new Container();
const b = new Container();
a.items.push(1);
console.log(a.items); // [1]
console.log(b.items); // []
Static Fields
Static fields belong to the class itself rather than instances. They're useful for class-level constants, factory registries, and shared state:
class Configuration {
static version = '1.0.0';
static defaultTimeout = 30000;
static maxRetries = 3;
}
console.log(Configuration.version); // '1.0.0'
console.log(Configuration.defaultTimeout); // 30000
Computed Field Names
Field names can be computed dynamically using expressions in square brackets. These are evaluated once at class definition time:
const fieldName = 'identifier';
class MyClass {
[fieldName] = 42;
}
const instance = new MyClass();
console.log(instance.identifier); // 42
Computed field names are useful for creating dynamic property keys based on configuration or constants.
Four ways to declare fields in modern JavaScript classes
Uninitialized Fields
Declare without value, defaults to undefined
Initialized Fields
Provide default values that are evaluated per instance
Static Fields
Class-level properties shared across all instances
Computed Names
Dynamic field names using expressions in brackets
Initialization Behavior and Timing
Understanding when and how fields are initialized is crucial for writing correct class hierarchies. The initialization order follows specific rules that affect how derived classes can reference base class properties.
Initialization Order
Instance fields are initialized in the order they are declared in the class body. This has important implications for inheritance:
- Base class fields are initialized before the base class constructor runs
- Derived class fields are initialized after
super()returns in the derived class constructor - Fields can reference other fields declared above them but not below
Accessing Fields in Constructors
Because instance fields are initialized before the constructor body runs (for base classes), you can access them within the constructor:
class Calculator {
value = 0;
multiplier = 2;
constructor() {
console.log(this.value); // 0 - field already initialized
this.result = this.value * this.multiplier;
}
}
const calc = new Calculator();
console.log(calc.result); // 0
However, base class constructors cannot access derived class fields because derived fields are initialized after super() returns:
class Base {
constructor() {
console.log(this.field); // undefined - not yet initialized
}
}
class Derived extends Base {
field = 42;
}
const instance = new Derived(); // Base constructor sees undefined
Field Initializers and 'this'
Within field initializers, this refers to the class instance being constructed, and super refers to the prototype of the base class. This allows fields to reference inherited properties during initialization:
class Base {
baseValue = 'base';
}
class Derived extends Base {
derivedValue = this.baseValue; // 'this' is the derived instance
superValue = super.baseValue; // Accesses Base.prototype.baseValue
}
const instance = new Derived();
console.log(instance.derivedValue); // 'base'
console.log(instance.superValue); // undefined (baseValue is an instance field, not prototype method)
1class Base {2 a = 1;3 b = this.a + 1; // Can reference 'a' - evaluated after 'a' is initialized4}5 6class Derived extends Base {7 c = this.a + 1; // Can reference inherited 'a'8 9 constructor() {10 super();11 // this.b and this.c are now accessible12 console.log(this.b); // 213 console.log(this.c); // 214 }15}16 17const instance = new Derived();Public vs Private Fields
Our web development services emphasize proper encapsulation patterns for maintainable codebases.
JavaScript provides both public and private field options, each serving different encapsulation needs. Understanding when to use each helps you design robust class APIs.
Public Fields (Default)
Public fields are the default and are accessible from anywhere in your code. They're part of your class's public contract and should be used when other code needs direct access to the value:
class BankAccount {
balance = 0; // Public field
accountNumber; // Public field
deposit(amount) {
this.balance += amount;
return this.balance;
}
}
const account = new BankAccount();
account.balance = 1000000; // Anyone can modify - not encapsulated
console.log(account.balance);
Private Fields (ES2022)
Private fields (prefixed with #) are only accessible within the class that defines them, providing true encapsulation and information hiding:
class BankAccount {
#balance = 0; // Private field
#accountNumber; // Private field
constructor(accountNumber) {
this.#accountNumber = accountNumber;
}
deposit(amount) {
if (amount <= 0) throw new Error('Invalid amount');
this.#balance += amount;
return this.#balance;
}
getBalance() {
return this.#balance; // Only accessible within class
}
}
const account = new BankAccount('12345');
console.log(account.#balance); // SyntaxError - private fields not accessible
console.log(account.getBalance()); // Works - through public method
When to Use Each
Use public fields when:
- The property should be part of the class's public API
- Other code needs direct access to the value
- The property is for configuration or metadata
- You're working with frameworks that expect public properties (like React state)
Use private fields when:
- The property is an internal implementation detail
- You need to enforce encapsulation
- Direct modification could break the object's invariants
- You're following information hiding principles
1// Public fields - accessible everywhere2class BankAccount {3 balance = 0; // Public field4 accountNumber; // Public field5 6 deposit(amount) {7 this.balance += amount;8 return this.balance;9 }10}11 12const account = new BankAccount();13account.balance = 1000000; // Anyone can modify - not encapsulated14 15// Private fields - only accessible within class16class SecureAccount {17 #balance = 0; // Private field18 #accountNumber; // Private field19 20 constructor(accountNumber) {21 this.#accountNumber = accountNumber;22 }23 24 deposit(amount) {25 if (amount <= 0) throw new Error('Invalid amount');26 this.#balance += amount;27 return this.#balance;28 }29 30 getBalance() {31 return this.#balance; // Only accessible within class32 }33}34 35const secure = new SecureAccount('12345');36secure.#balance; // SyntaxError - private fields not accessiblePublic Fields vs Constructor Assignment
A critical distinction exists between public class fields and assigning properties in the constructor. When you declare a public class field, JavaScript uses [[DefineOwnProperty]] semantics, which is similar to Object.defineProperty(). This differs from regular assignment which uses [[Set]] semantics.
Class Fields Use [[DefineOwnProperty]]
When you declare a public class field, it does not trigger setters in parent classes. This behavior can be surprising but is by design:
class Base {
set field(value) {
console.log('Setter called with:', value);
}
}
class Derived extends Base {
field = 1; // Does NOT call the setter
}
const instance = new Derived(); // No log - setter not called
Constructor Assignment Uses [[Set]]
When you assign in the constructor, it uses [[Set]] semantics, which does call setters defined in parent classes:
class Base {
set field(value) {
console.log('Setter called with:', value);
}
}
class Derived extends Base {
constructor() {
super();
this.field = 1; // Calls the setter
}
}
const instance = new Derived(); // Logs: Setter called with: 1
Practical Implications
This difference matters in several scenarios:
- Inheritance chains with setters in parent classes - class fields won't trigger inherited setters
- Proxies that trap property assignments - different traps are triggered
- Decorators that observe property definitions -
Object.definePropertyvs assignment behavior - Property getters/setters that have side effects - unexpected behavior with class fields
The class field approach is generally preferred because it's more explicit about defining the property and avoids accidental side effects in inheritance scenarios. For more details, see the MDN guide on using classes.
Common Use Cases
Whether you're building with React or Node.js, these patterns apply across the JavaScript ecosystem. Understanding how public fields interact with React component re-renders is essential for building performant applications.
Public class fields shine in several practical scenarios that developers encounter regularly. Understanding these patterns helps you apply the feature effectively.
React Component State
Public class fields are commonly used in React class components for state. This pattern is widespread in existing React applications and provides a clean way to define initial state:
class Counter extends React.Component {
state = {
count: 0
};
increment = () => {
this.setState(prevState => ({
count: prevState.count + 1
}));
};
}
Configuration and Defaults
Public fields excel at defining default configuration. This pattern is useful for API clients, service classes, and any component with configurable options:
class APIClient {
baseUrl = 'https://api.example.com';
timeout = 30000;
retries = 3;
authToken = null;
constructor(options = {}) {
Object.assign(this, options); // Override defaults with passed options
}
}
Class Metadata and Constants
Static fields are perfect for class-level constants and metadata. This approach keeps related values organized and easily accessible:
class MathUtils {
static PI = 3.14159;
static E = 2.71828;
static VERSION = '2.1.0';
static calculateArea(radius) {
return MathUtils.PI * radius * radius;
}
}
Factory and Registry Patterns
Public fields support factory and registry patterns commonly used in plugin systems and component libraries:
class Widget {
static registry = new Map();
static register(name, WidgetClass) {
Widget.registry.set(name, WidgetClass);
}
static create(config) {
const WidgetClass = Widget.registry.get(config.type);
return new WidgetClass(config);
}
}
1// React Class Component State2class Counter extends React.Component {3 state = {4 count: 05 };6 7 increment = () => {8 this.setState(prevState => ({9 count: prevState.count + 110 }));11 };12}13 14// Configuration and Defaults15class APIClient {16 baseUrl = 'https://api.example.com';17 timeout = 30000;18 retries = 3;19 authToken = null;20 21 constructor(options = {}) {22 Object.assign(this, options);23 }24}25 26// Factory and Registry Pattern27class Widget {28 static registry = new Map();29 30 static register(name, WidgetClass) {31 Widget.registry.set(name, WidgetClass);32 }33 34 static create(config) {35 const WidgetClass = Widget.registry.get(config.type);36 return new WidgetClass(config);37 }38}Performance Considerations
Modern JavaScript engines optimize public class fields effectively, making them a performant choice for defining class properties. Understanding these optimizations helps you write efficient code.
Engine Optimization
The declarative nature of field declarations allows JavaScript engines to optimize more effectively:
- Pre-allocate memory for known field slots during class definition
- Inline field access when possible, reducing property lookup overhead
- Optimize initialization since field order is fixed at definition time
Bundle Size Benefits
Using public fields often reduces bundle size compared to constructor assignments, especially when minified:
// Before (ES5 pattern)
class Counter {
constructor() {
this.count = 0;
this.name = 'Counter';
}
}
// After (ES2022 public fields)
class Counter {
count = 0;
name = 'Counter';
}
The public field syntax is more concise and often results in smaller output after minification.
Comparison with Closures
For data that shouldn't be exposed, private fields are often preferred over closures for performance reasons. Closures create a new function per instance, while private fields share methods across instances:
// Less efficient (closure per instance)
class Counter {
constructor() {
let count = 0;
this.increment = () => {
count++;
return count;
};
}
}
// More efficient (shared method, private field)
class Counter {
#count = 0;
increment() {
return ++this.#count;
}
}
For applications built with our JavaScript development services, choosing the right pattern for data encapsulation can significantly impact performance at scale.
Best Practices
For teams implementing these patterns at scale, our web development team can provide guidance on enterprise-class code organization.
Following consistent conventions when using public fields makes your code more readable and maintainable. These guidelines reflect established patterns in the JavaScript community.
Naming Conventions
- Use camelCase for field names (standard JavaScript convention)
- Use UPPER_SNAKE_CASE for static constants that shouldn't change
- Prefix private fields with
#to indicate internal state - Choose descriptive names that indicate purpose and type
Initialization Patterns
// Good: Initialize with meaningful defaults
class User {
isActive = true;
role = 'guest';
permissions = [];
}
// Good: Initialize from constructor parameters
class User {
constructor(name, email) {
this.name = name;
this.email = email;
this.createdAt = new Date();
}
}
// Avoid: Uninitialized fields if they will always be set
class Example {
id; // Only if you always set it in constructor
}
Inheritance Considerations
When designing classes for extension, document which fields subclasses may override and use conventions to indicate intended visibility:
// Good: Document that subclasses may override
class Plugin {
name = 'base-plugin';
version = '1.0.0';
initialize() {
// Base initialization
}
}
// Good: Use protected convention (by convention, not language-enforced)
class BaseWidget {
_isMounted = false; // Protected - for subclass use only
mount() {
this._isMounted = true;
}
}
Combining with Private Fields
A common pattern is to expose configuration through public fields while keeping internal state private:
class SecureStore {
// Public configuration
name = 'Default Store';
autoSave = true;
// Private internal state
#data = new Map();
#isDirty = false;
save() {
if (!this.#isDirty) return;
// Save logic
this.#isDirty = false;
}
setItem(key, value) {
this.#data.set(key, value);
this.#isDirty = true;
if (this.autoSave) this.save();
}
}
Frequently Asked Questions
Are public class fields supported in all browsers?
Yes, public class fields are part of ES2022 and have universal support in all modern browsers including Chrome, Firefox, Safari, Edge, and Node.js 12+.
What's the difference between public and private fields?
Public fields are accessible from anywhere, while private fields (prefixed with #) are only accessible within the class that defines them. Private fields provide true encapsulation.
Can I use computed names for public fields?
Yes, you can use expressions in square brackets for computed field names. These are evaluated once at class definition time.
When are public fields initialized?
Instance fields are initialized before the constructor runs (for base classes) or after super() returns (for derived classes). Static fields are initialized when the class is defined.
Do public fields call setters in parent classes?
No, public class fields use [[DefineOwnProperty]] semantics which do not trigger setters. This differs from assigning in the constructor which uses [[Set]] semantics.
Summary
Public class fields are a standardized JavaScript feature that provides a declarative way to define properties on classes.
Key Takeaways
- ES2022 Standard: Public class fields are widely supported across all modern JavaScript environments
- Clean Syntax: Declare properties directly in the class body rather than in constructors
- Instance and Static: Support both instance fields (per-object) and static fields (per-class)
- Predictable Initialization: Field initializers run at predictable times during object creation
- Public by Default: Unlike private fields (#), public fields are accessible anywhere
- [[DefineOwnProperty]] Semantics: Field declarations use different semantics than constructor assignments
- Framework Integration: Widely used in React, Angular, and other frameworks
- Performance: Modern engines optimize public fields effectively
Public class fields represent a significant improvement in JavaScript's class syntax, making code more readable, self-documenting, and aligned with other object-oriented languages. Whether you're building React components, designing library APIs, or organizing application code, public fields provide a clean and efficient way to define class properties. For teams working with modern JavaScript frameworks, mastering public fields is essential for writing maintainable, performant code that follows current best practices.
Our web development services team specializes in implementing these patterns across enterprise applications.
Sources
- MDN Web Docs: Public Class Fields - Primary reference for syntax, behavior, and examples
- MDN Web Docs: Using Classes - Guide for context and practical usage
- MDN Web Docs: Private Class Elements - Complementary topic for comparison
- TypeScript Handbook: Classes - TypeScript-specific considerations
- ECMAScript 2022 Language Specification - Official ECMAScript specification