JavaScript 60 🧬 Class fields — public/private/static
class Counter {
count = 0;
increment() {
this.count++;
return this.count;
}
}
const c = new Counter();
console.log(c.count);
console.log(c.increment());
console.log(c.count);
class BankAccount {
#balance = 0;
constructor(initial) {
this.#balance = initial;
}
deposit(amount) {
this.#balance += amount;
return this.#balance;
}
get balance() {
return this.#balance;
}
}
const account = new BankAccount(100);
console.log(account.balance);
console.log(account.deposit(50));
try {
console.log(account.#balance);
} catch (err) {
console.log(err.message);
}
class Config {
static version = '1.0.0';
static instances = 0;
constructor() {
Config.instances++;
}
static reset() {
Config.instances = 0;
}
}
console.log(Config.version);
new Config();
new Config();
console.log(Config.instances);
Config.reset();
console.log(Config.instances);
class Temperature {
#celsius;
constructor(celsius) {
this.#celsius = celsius;
}
get fahrenheit() {
return this.#celsius * 9 / 5 + 32;
}
set fahrenheit(f) {
this.#celsius = (f - 32) * 5 / 9;
}
}
const t = new Temperature(25);
console.log(t.fahrenheit);
t.fahrenheit = 100;
console.log(t.fahrenheit);
class StaticInit {
static data;
static {
console.log('Initializing class');
StaticInit.data = new Map();
StaticInit.data.set('loaded', true);
}
}
console.log(StaticInit.data.get('loaded'));
Class fields are the modern way to declare properties on a class. They replace constructor assignments, give you true private fields with #, and provide static members for class-level data. Combined with static initialization blocks, they make class definitions cleaner and more powerful.
Key point: Class fields are per-instance by default. #private fields are truly private — not accessible outside the class body, not even via reflection. static fields belong to the class itself, not instances.
a – Public instance fields
Public fields are declared directly in the class body — no constructor needed. Each instance gets its own copy.
Basic syntax:
class Counter {
count = 0;
increment() {
this.count++;
return this.count;
}
}
The count = 0 field is initialized for every instance.
const c = new Counter();
console.log(c.count);
// [ 0 ]
c.increment();
console.log(c.count);
// [ 1 ]
Fields with expressions:
class User {
id = Math.random();
createdAt = new Date();
tags = [];
settings = { theme: 'light' };
}
Every instance gets its own id, createdAt, tags, and settings.
The object-literal independence:
const a = new User();
const b = new User();
a.tags.push('admin');
console.log(a.tags);
// [ [ 'admin' ] ]
console.log(b.tags);
// [ [] ] ← separate array
Each instance has its own array. No sharing — unless you use static.
Fields vs constructor assignment:
// Old way
class Old {
constructor() {
this.count = 0;
this.name = 'unknown';
}
}
// New way
class Modern {
count = 0;
name = 'unknown';
}
Both produce the same instance shape. Class fields run in order, before the constructor body.
Initialization order:
class Order {
a = console.log('a');
b = console.log('b');
constructor() {
console.log('constructor');
}
}
new Order();
// [ 'a' ]
// [ 'b' ]
// [ 'constructor' ]
Fields initialize before the constructor body runs.
Fields with this:
class Person {
firstName = 'Alice';
lastName = 'Smith';
fullName = `${this.firstName} ${this.lastName}`;
}
const p = new Person();
console.log(p.fullName);
// [ 'Alice Smith' ]
Fields can reference earlier fields via this.
Fields with arrow functions:
class Button {
handleClick = () => {
console.log('Clicked');
};
}
Arrow function fields capture this — great for event handlers.
class Counter {
count = 0;
increment = () => {
this.count++;
};
}
const c = new Counter();
const fn = c.increment;
fn(); // ✅ works — `this` is bound
console.log(c.count);
// [ 1 ]
Without arrow, a detached method would lose this.
Advantages of public fields:
- Concise — no constructor boilerplate
- Clear — field declarations read as a schema
- Ordered — initialization order is predictable
- Bindable — arrow function fields capture
this
Common patterns:
class Connection {
url = '';
timeout = 30000;
retries = 3;
handlers = [];
constructor(url) {
this.url = url;
}
register(handler) {
this.handlers.push(handler);
}
}
b – Private fields
Private fields start with # and are truly private. Outside the class, they’re inaccessible — not by direct access, not by reflection, not by proxy tricks.
Basic syntax:
class BankAccount {
#balance = 0;
constructor(initial) {
this.#balance = initial;
}
deposit(amount) {
this.#balance += amount;
return this.#balance;
}
get balance() {
return this.#balance;
}
}
#balance can only be read or written inside the class body.
Outside access throws:
const account = new BankAccount(100);
console.log(account.balance);
// [ 100 ] ✅ via getter
try {
console.log(account.#balance);
} catch (err) {
console.log(err.message);
}
// [ Private field '#balance' must be declared in an enclosing class ]
Not even a runtime error about access — a parse-time syntax error in most cases.
Not visible in reflection:
console.log(Object.keys(account));
// [ [] ]
console.log(Object.getOwnPropertyNames(account));
// [ [] ]
console.log(JSON.stringify(account));
// [ '{}' ]
Private fields don’t appear in enumeration, Object.keys, or JSON.
Private fields with methods:
class User {
#password;
constructor(password) {
this.#password = password;
}
#hash(input) {
return input.split('').reverse().join('');
}
check(input) {
return this.#hash(input) === this.#password;
}
}
#hash is a private method. Only callable inside the class.
Private static fields:
class Counter {
static #instances = 0;
constructor() {
Counter.#instances++;
}
static get instances() {
return Counter.#instances;
}
}
new Counter();
new Counter();
console.log(Counter.instances);
// [ 2 ]
When to use private fields:
| Use case | Why |
|---|---|
| Internal state | Hide from outside |
| Sensitive data | Prevent reading/modifying |
| Implementation details | Safe to refactor |
| Invariants | Enforce via accessors |
| Brand checks | #field in obj |
Private field brand checks (#x in obj):
class Token {
#secret;
constructor() {
this.#secret = true;
}
static isToken(obj) {
return #secret in obj;
}
}
console.log(Token.isToken(new Token()));
// [ true ]
console.log(Token.isToken({}));
// [ false ]
#field in obj is a brand check — it verifies the object has the private field. No way to fake it from outside.
Private field restrictions:
- Must be declared in the class body
- Can’t be added dynamically
- Can’t be accessed via
this['#field']— the#is part of the syntax - Cannot be inherited — each class has its own private fields
Subclasses can’t access parent’s privates:
class Parent {
#secret = 'parent';
}
class Child extends Parent {
read() {
return this.#secret;
// SyntaxError: Private field '#secret' must be declared in an enclosing class
}
}
Private is class-scoped, not instance-scoped.
Private methods and accessors:
class Validator {
#rules = [];
#addRule(rule) {
this.#rules.push(rule);
}
addRule(rule) {
this.#addRule(rule);
return this;
}
validate(value) {
return this.#rules.every(r => r(value));
}
}
Private getters and setters:
class Temperature {
#celsius = 0;
get #fahrenheit() {
return this.#celsius * 9 / 5 + 32;
}
set #fahrenheit(f) {
this.#celsius = (f - 32) * 5 / 9;
}
toF() {
return this.#fahrenheit;
}
}
Rare but valid.
Private fields vs Symbols:
| Feature | Private # | Symbol |
|---|---|---|
| Truly private | ✅ | ❌ |
| Reflection | ❌ | ✅ |
| Inherited | ❌ | ✅ |
| Syntax | #name | Symbol() |
| Cross-module | ❌ | ✅ (with Symbol.for) |
Symbols hide by convention. Private fields hide by language design.
Private fields vs closures:
// Old way — closure
function createUser(name) {
let secret = name;
return {
getName: () => secret
};
}
// New way — private field
class User {
#name;
constructor(name) {
this.#name = name;
}
getName() {
return this.#name;
}
}
Closures work but prevent method sharing. Private fields don’t.
c – Static fields and static blocks
Static fields belong to the class, not instances. Static blocks run once when the class is defined.
Basic static field:
class Config {
static version = '1.0.0';
static defaults = { theme: 'light' };
}
console.log(Config.version);
// [ '1.0.0' ]
console.log(Config.defaults.theme);
// [ 'light' ]
Access via the class, not an instance.
Static doesn’t appear on instances:
const c = new Config();
console.log(c.version);
// [ undefined ]
console.log(c.constructor.version);
// [ '1.0.0' ] ← via the class
Static methods:
class MathUtils {
static square(n) {
return n * n;
}
static cube(n) {
return n * n * n;
}
}
console.log(MathUtils.square(4));
// [ 16 ]
Static counters:
class User {
static count = 0;
constructor(name) {
this.name = name;
User.count++;
}
}
new User('Alice');
new User('Bob');
console.log(User.count);
// [ 2 ]
Static with private:
class Counter {
static #instances = 0;
constructor() {
Counter.#instances++;
}
static getInstances() {
return Counter.#instances;
}
}
Static methods calling private fields:
class Registry {
static #items = [];
static add(item) {
Registry.#items.push(item);
}
static get items() {
return [...Registry.#items];
}
}
Registry.add('a');
Registry.add('b');
console.log(Registry.items);
// [ [ 'a', 'b' ] ]
Static initialization blocks:
Static blocks run once when the class is defined — perfect for complex initialization that needs multiple statements.
class StaticInit {
static data;
static {
console.log('Initializing class');
StaticInit.data = new Map();
StaticInit.data.set('loaded', true);
}
}
console.log(StaticInit.data.get('loaded'));
// [ 'Initializing class' ]
// [ true ]
Why static blocks matter:
Before static blocks, complex initialization required IIFEs or external code:
// Old way
class Old {
static config = (() => {
const cfg = {};
// multiple lines
return cfg;
})();
}
// New way
class Modern {
static config;
static {
const cfg = {};
// multiple lines
Modern.config = cfg;
}
}
Multiple static blocks:
class Multi {
static a = 1;
static {
Multi.a++;
}
static b = Multi.a + 1;
static {
Multi.b *= 2;
}
}
console.log(Multi.a, Multi.b);
// [ 2, 6 ]
Static blocks run in order, interleaved with static field initializers.
Static block with this:
Inside a static block, this refers to the class:
class Calculator {
static factor = 2;
static {
this.doubled = this.factor * 2;
}
}
console.log(Calculator.doubled);
// [ 4 ]
Static block with error handling:
class Config {
static data;
static {
try {
Config.data = JSON.parse('{ invalid }');
} catch {
Config.data = {};
console.log('Using defaults');
}
}
}
Static fields inherited:
class Base {
static version = '1.0';
}
class Derived extends Base {}
console.log(Derived.version);
// [ '1.0' ] ← inherited
When to use static:
| Use case | Example |
|---|---|
| Constants | static PI = 3.14 |
| Factory methods | static from(json) |
| Counters | static instances = 0 |
| Cached data | static cache = new Map() |
| Utility methods | static parse(str) |
| Singletons | static get instance() |
| Registry | static #all = [] |
Complete example — factory pattern:
class Point {
static #origin = null;
constructor(x, y) {
this.x = x;
this.y = y;
}
static from({ x, y }) {
return new Point(x, y);
}
static origin() {
Point.#origin ??= new Point(0, 0);
return Point.#origin;
}
static distance(a, b) {
return Math.hypot(a.x - b.x, a.y - b.y);
}
}
const p = Point.from({ x: 3, y: 4 });
console.log(p);
// [ Point { x: 3, y: 4 } ]
const o = Point.origin();
console.log(o);
// [ Point { x: 0, y: 0 } ]
Static fields and the this in static methods:
Inside static methods, this refers to the class:
class Base {
static name = 'Base';
static describe() {
return `I am ${this.name}`;
}
}
class Derived extends Base {
static name = 'Derived';
}
console.log(Base.describe());
// [ 'I am Base' ]
console.log(Derived.describe());
// [ 'I am Derived' ] ← this is Derived
The this in static methods is the class that called them — supports subclass overrides.
Complete Example Session
// ============================================
// PART 1: PUBLIC INSTANCE FIELDS
// ============================================
class Counter {
count = 0;
increment() {
this.count++;
return this.count;
}
}
const c = new Counter();
console.log(c.count);
// [ 0 ]
c.increment();
console.log(c.count);
// [ 1 ]
// ============================================
// PART 2: INDEPENDENT FIELDS
// ============================================
class User {
tags = [];
}
const a = new User();
const b = new User();
a.tags.push('admin');
console.log(a.tags);
// [ [ 'admin' ] ]
console.log(b.tags);
// [ [] ]
// ============================================
// PART 3: FIELD INIT ORDER
// ============================================
class Order {
a = console.log('a');
b = console.log('b');
constructor() {
console.log('constructor');
}
}
new Order();
// [ 'a' ]
// [ 'b' ]
// [ 'constructor' ]
// ============================================
// PART 4: FIELD WITH THIS
// ============================================
class Person {
firstName = 'Alice';
lastName = 'Smith';
fullName = `${this.firstName} ${this.lastName}`;
}
console.log(new Person().fullName);
// [ 'Alice Smith' ]
// ============================================
// PART 5: ARROW FIELD
// ============================================
class Clicker {
count = 0;
handle = () => {
this.count++;
};
}
const clicker = new Clicker();
const fn = clicker.handle;
fn();
console.log(clicker.count);
// [ 1 ]
// ============================================
// PART 6: PRIVATE FIELD
// ============================================
class BankAccount {
#balance = 0;
constructor(initial) {
this.#balance = initial;
}
deposit(amount) {
this.#balance += amount;
return this.#balance;
}
get balance() {
return this.#balance;
}
}
const account = new BankAccount(100);
console.log(account.balance);
// [ 100 ]
console.log(account.deposit(50));
// [ 150 ]
// ============================================
// PART 7: PRIVATE FIELD ACCESS OUTSIDE
// ============================================
try {
eval('account.#balance');
} catch (err) {
console.log(err.message);
}
// [ Private field '#balance' must be declared in an enclosing class ]
// ============================================
// PART 8: PRIVATE NOT ENUMERABLE
// ============================================
console.log(Object.keys(account));
// [ [] ]
console.log(JSON.stringify(account));
// [ '{}' ]
// ============================================
// PART 9: PRIVATE METHOD
// ============================================
class User2 {
#password;
constructor(password) {
this.#password = password;
}
#hash(input) {
return input.split('').reverse().join('');
}
check(input) {
return this.#hash(input) === this.#password;
}
}
const u2 = new User2('abc');
console.log(u2.check('abc'));
// [ true ]
// ============================================
// PART 10: PRIVATE STATIC
// ============================================
class Counted {
static #instances = 0;
constructor() {
Counted.#instances++;
}
static get instances() {
return Counted.#instances;
}
}
new Counted();
new Counted();
console.log(Counted.instances);
// [ 2 ]
// ============================================
// PART 11: BRAND CHECK
// ============================================
class Token {
#secret;
constructor() {
this.#secret = true;
}
static isToken(obj) {
return #secret in obj;
}
}
console.log(Token.isToken(new Token()));
// [ true ]
console.log(Token.isToken({}));
// [ false ]
// ============================================
// PART 12: STATIC FIELD
// ============================================
class Config {
static version = '1.0.0';
}
console.log(Config.version);
// [ '1.0.0' ]
// ============================================
// PART 13: STATIC NOT ON INSTANCE
// ============================================
const c2 = new Config();
console.log(c2.version);
// [ undefined ]
console.log(c2.constructor.version);
// [ '1.0.0' ]
// ============================================
// PART 14: STATIC METHOD
// ============================================
class MathUtils {
static square(n) {
return n * n;
}
}
console.log(MathUtils.square(4));
// [ 16 ]
// ============================================
// PART 15: STATIC COUNTER
// ============================================
class Person3 {
static count = 0;
constructor() {
Person3.count++;
}
}
new Person3();
new Person3();
console.log(Person3.count);
// [ 2 ]
// ============================================
// PART 16: STATIC BLOCK
// ============================================
class StaticInit {
static data;
static {
console.log('init');
StaticInit.data = new Map([['loaded', true]]);
}
}
// [ 'init' ]
console.log(StaticInit.data.get('loaded'));
// [ true ]
// ============================================
// PART 17: MULTIPLE STATIC BLOCKS
// ============================================
class Multi {
static a = 1;
static {
Multi.a++;
}
static b = Multi.a + 1;
static {
Multi.b *= 2;
}
}
console.log(Multi.a, Multi.b);
// [ 2, 6 ]
// ============================================
// PART 18: STATIC BLOCK WITH THIS
// ============================================
class Calculator {
static factor = 2;
static {
this.doubled = this.factor * 2;
}
}
console.log(Calculator.doubled);
// [ 4 ]
// ============================================
// PART 19: STATIC INHERITANCE
// ============================================
class Base {
static version = '1.0';
}
class Derived extends Base {}
console.log(Derived.version);
// [ '1.0' ]
// ============================================
// PART 20: FULL SCRIPT
// ============================================
class Counter60 {
count = 0;
increment() {
this.count++;
return this.count;
}
}
const c60 = new Counter60();
console.log(c60.count);
console.log(c60.increment());
console.log(c60.count);
class BankAccount60 {
#balance = 0;
constructor(initial) {
this.#balance = initial;
}
deposit(amount) {
this.#balance += amount;
return this.#balance;
}
get balance() {
return this.#balance;
}
}
const account60 = new BankAccount60(100);
console.log(account60.balance);
console.log(account60.deposit(50));
try {
console.log(account60.#balance);
} catch (err) {
console.log(err.message);
}
class Config60 {
static version = '1.0.0';
static instances = 0;
constructor() {
Config60.instances++;
}
static reset() {
Config60.instances = 0;
}
}
console.log(Config60.version);
new Config60();
new Config60();
console.log(Config60.instances);
Config60.reset();
console.log(Config60.instances);
class Temperature60 {
#celsius;
constructor(celsius) {
this.#celsius = celsius;
}
get fahrenheit() {
return this.#celsius * 9 / 5 + 32;
}
set fahrenheit(f) {
this.#celsius = (f - 32) * 5 / 9;
}
}
const t60 = new Temperature60(25);
console.log(t60.fahrenheit);
t60.fahrenheit = 100;
console.log(t60.fahrenheit);
class StaticInit60 {
static data;
static {
console.log('Initializing class');
StaticInit60.data = new Map();
StaticInit60.data.set('loaded', true);
}
}
console.log(StaticInit60.data.get('loaded'));
Quick Reference
Field Types
| Type | Syntax | Accessible |
|---|---|---|
| Public instance | field = value | Anywhere via obj.field |
| Private instance | #field = value | Only inside class |
| Public static | static field = value | Via Class.field |
| Private static | static #field = value | Only inside class |
| Public method | method() {} | Anywhere |
| Private method | #method() {} | Only inside class |
| Static method | static method() {} | Via Class.method() |
Public Fields
| Feature | Meaning |
|---|---|
| Per instance | Each instance gets its own |
| Initialized before constructor | Order matters |
this available | Can reference earlier fields |
| Arrow functions | Bind this to instance |
Private Fields
| Feature | Meaning |
|---|---|
# prefix | Part of the name |
| Truly private | Not accessible outside |
| Not enumerable | Hidden from Object.keys |
| Not inherited | Subclasses don’t see parent privates |
| Brand check | #field in obj |
| No dynamic add | Must be declared |
Static Fields
| Feature | Meaning |
|---|---|
| On the class | Not on instances |
| Inherited | Subclasses see them |
| Private allowed | static #x |
| Blocks allowed | static { ... } |
this in methods | Refers to calling class |
Static Blocks
| Feature | Meaning |
|---|---|
| Runs once | When class is defined |
Can use this | Refers to class |
| Order matters | Interleaved with fields |
| Multiple | Allowed, run in order |
Public vs Private vs Static
| Feature | Public | Private | Static |
|---|---|---|---|
| Per instance | ✅ | ✅ | ❌ |
| On class | ❌ | ❌ | ✅ |
| Outside access | ✅ | ❌ | Via class |
| Reflection | ✅ | ❌ | ✅ |
| Inheritance | ✅ | ❌ | ✅ |
# prefix | ❌ | ✅ | Optional |
Field Initialization Order
| Step | What runs |
|---|---|
| 1 | Static fields and blocks |
| 2 | Instance field initializers |
| 3 | Constructor body |
Comparison with Other Languages
| Feature | JavaScript | TypeScript | Java |
|---|---|---|---|
| Private | #x | private x (compile) | private x |
| Truly private | ✅ | ❌ (runtime) | ✅ |
| Public fields | ✅ | ✅ | ✅ |
| Static | ✅ | ✅ | ✅ |
| Static blocks | ✅ | ✅ | ✅ |
Best Practices
✅ Do This:
// Use fields instead of constructor assignments
class User {
name = '';
age = 0;
} // ✅
// Use # for truly private
class Account {
#balance = 0;
} // ✅
// Use arrow functions for callbacks
class Button {
click = () => this.handle();
} // ✅
// Use static for shared constants
class Config {
static VERSION = '1.0';
} // ✅
// Use static blocks for complex init
class Registry {
static #items;
static { Registry.#items = new Map(); }
} // ✅
// Brand checks with #field in obj
static isUser(obj) {
return #name in obj;
} // ✅
// Chain static methods for fluent APIs
return this.parse(x).validate(); // ✅
❌ Don’t Do This:
// Don't use # in dynamic access
obj['#field']; // ❌ undefined
// Don't expect inheritance of private
class Child extends Parent {
read() { return this.#parentField; } // ❌ SyntaxError
} // ✅ use a protected getter
// Don't use static for per-instance data
class Counter {
static count = 0; // ⚠️ shared
constructor() { this.count++; } // modifies instance
} // ✅ use instance field
// Don't rely on private appearing in reflection
Object.keys(instance); // ❌ won't include #fields
// Don't use private if you need external access
class Exposed {
#field = 1; // ⚠️ no external access
} // ✅ use a getter
// Don't initialize private static in constructor
constructor() {
static.#x = 1; // ❌ not how it works
} // ✅ use static block
// Don't forget field order matters
class Order {
a = this.b; // ⚠️ b is undefined here
b = 1;
}
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
| Private inherited | SyntaxError | Use protected getters |
| Field order | Undefined values | Declare dependencies first |
| Static on instance | undefined | Use this.constructor |
Dynamic #field | Not accessible | Only #field in obj |
| Private in JSON | Skipped | Provide a toJSON() |
this in static | Confusing | It’s the calling class |
| Multiple static blocks order | Misunderstanding | Runs top-to-bottom |
| Arrow fields vs methods | Different this | Arrow binds; method doesn’t |
Real-World Examples
1. Public Instance Field
class Counter {
count = 0;
}
const c = new Counter();
console.log(c.count);
// [ 0 ]
2. Independent Fields
class User {
tags = [];
}
const a = new User();
const b = new User();
a.tags.push('x');
console.log(b.tags);
// [ [] ]
3. Field with this
class Person {
first = 'Alice';
last = 'Smith';
full = `${this.first} ${this.last}`;
}
console.log(new Person().full);
// [ 'Alice Smith' ]
4. Arrow Field Binds this
class Clicker {
count = 0;
handle = () => this.count++;
}
5. Private Field
class Account {
#balance = 0;
deposit(n) {
this.#balance += n;
}
get balance() {
return this.#balance;
}
}
6. Private Access Throws
try {
new Account().#balance;
} catch (err) {
console.log(err.message);
}
// [ Private field ... ]
7. Private Method
class User {
#hash(s) { return s.split('').reverse().join(''); }
check(s, hash) { return this.#hash(s) === hash; }
}
8. Static Field
class Config {
static version = '1.0';
}
console.log(Config.version);
// [ '1.0' ]
9. Static Not on Instance
const c = new Config();
console.log(c.version);
// [ undefined ]
10. Static Method
class MathUtils {
static square(n) { return n * n; }
}
console.log(MathUtils.square(4));
// [ 16 ]
11. Static Counter
class User {
static count = 0;
constructor() { User.count++; }
}
new User();
new User();
console.log(User.count);
// [ 2 ]
12. Private Static
class Counter {
static #instances = 0;
constructor() { Counter.#instances++; }
static get instances() { return Counter.#instances; }
}
13. Brand Check
class Token {
#secret;
constructor() { this.#secret = true; }
static is(obj) { return #secret in obj; }
}
14. Static Block
class Init {
static data;
static {
Init.data = new Map([['ready', true]]);
}
}
15. Multiple Static Blocks
class M {
static a = 1;
static { M.a++; }
static b = M.a + 1;
}
16. Static Block with this
class Calculator {
static factor = 2;
static {
this.doubled = this.factor * 2;
}
}
17. Static Inheritance
class Base { static version = '1.0'; }
class Derived extends Base {}
console.log(Derived.version);
// [ '1.0' ]
18. Factory Method
class Point {
constructor(x, y) { this.x = x; this.y = y; }
static from({ x, y }) { return new Point(x, y); }
}
19. Singleton via Static
class Config {
static #instance;
static get() {
return Config.#instance ??= new Config();
}
}
20. Full Script
class Counter60 {
count = 0;
increment() {
this.count++;
return this.count;
}
}
const c60 = new Counter60();
console.log(c60.count);
console.log(c60.increment());
console.log(c60.count);
class BankAccount60 {
#balance = 0;
constructor(initial) {
this.#balance = initial;
}
deposit(amount) {
this.#balance += amount;
return this.#balance;
}
get balance() {
return this.#balance;
}
}
const account60 = new BankAccount60(100);
console.log(account60.balance);
console.log(account60.deposit(50));
class Config60 {
static version = '1.0.0';
static instances = 0;
constructor() {
Config60.instances++;
}
static reset() {
Config60.instances = 0;
}
}
console.log(Config60.version);
new Config60();
new Config60();
console.log(Config60.instances);
Config60.reset();
console.log(Config60.instances);
class Temperature60 {
#celsius;
constructor(celsius) {
this.#celsius = celsius;
}
get fahrenheit() {
return this.#celsius * 9 / 5 + 32;
}
set fahrenheit(f) {
this.#celsius = (f - 32) * 5 / 9;
}
}
const t60 = new Temperature60(25);
console.log(t60.fahrenheit);
t60.fahrenheit = 100;
console.log(t60.fahrenheit);
class StaticInit60 {
static data;
static {
console.log('Initializing class');
StaticInit60.data = new Map();
StaticInit60.data.set('loaded', true);
}
}
console.log(StaticInit60.data.get('loaded'));
Visual: Field Types
┌──────────────────────────────────────────────┐
│ class Example { │
│ publicField = 1; ← per instance │
│ #privateField = 2; ← per instance │
│ │
│ static publicStatic = 3; ← on class │
│ static #privateStatic = 4; ← on class │
│ │
│ static { ← runs once │
│ // init code │
│ } │
│ } │
│ │
└──────────────────────────────────────────────┘
Visual: Private vs Public
┌──────────────────────────────────────────────┐
│ class Account { │
│ #balance = 100; ← private │
│ owner = 'Alice'; ← public │
│ } │
│ │
│ const a = new Account(); │
│ │
│ a.owner → 'Alice' ✅ │
│ a.#balance → SyntaxError ❌ │
│ Object.keys(a) → ['owner'] │
│ JSON.stringify(a) → '{"owner":"Alice"}' │
│ │
└──────────────────────────────────────────────┘
Visual: Initialization Order
┌──────────────────────────────────────────────┐
│ 1. Static fields (class definition time) │
│ 2. Static blocks │
│ 3. Static methods (defined, not called) │
│ │
│ When new Class() is called: │
│ │
│ 4. Instance field initializers (in order) │
│ 5. Constructor body │
│ │
└──────────────────────────────────────────────┘
Visual: Static Blocks
┌──────────────────────────────────────────────┐
│ class Registry { │
│ static items; │
│ │
│ static { │
│ Registry.items = new Map(); │
│ Registry.items.set('a', 1); │
│ } │
│ │
│ static other = compute(); │
│ │
│ static { │
│ Registry.other += 1; │
│ } │
│ } │
│ │
│ Runs in order — top to bottom │
│ Once, at class definition time │
│ │
└──────────────────────────────────────────────┘
Summary
| Concept | Syntax | Example |
|---|---|---|
| Public field | field = value | count = 0 |
| Private field | #field = value | #balance = 0 |
| Public static | static field = value | static version = '1.0' |
| Private static | static #field = value | static #instances = 0 |
| Public method | method() {} | increment() {} |
| Private method | #method() {} | #hash() {} |
| Static method | static method() {} | static square(n) {} |
| Static block | static { ... } | Init code |
| Brand check | #field in obj | Token.is(obj) |
| Arrow field | field = () => {} | Bound this |
Key takeaways:
- Class fields declare properties directly in the class body
- Public fields are per-instance and initialized before the constructor
#privatefields are truly private — inaccessible outside the class, not enumerated, not reflected- Private methods use
#name()and can only be called inside the class #field in objis the brand check — verifies an object has a private field- Static fields belong to the class, not instances — shared across all uses
- Static methods are called via
Class.method(), andthisrefers to the calling class - Static blocks run once at class definition time — perfect for complex init
- Private is not inherited — subclasses can’t access parent’s
#fields - Arrow function fields bind
this— great for event handlers and callbacks - Field order matters — earlier fields can reference earlier fields via
this - Private fields are safer than Symbols — they’re enforced by the language, not convention
Remember: Class fields modernize how you declare properties. Use public fields for normal data, #private for internal state, and static for class-level values. Use static blocks for anything that needs multiple statements. Remember that private is class-scoped, not instance-scoped — subclasses can’t peek. And know the initialization order: statics first, then instance fields, then the constructor. Master class fields, and your classes become cleaner, safer, and more expressive.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!