| |

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 caseWhy
Internal stateHide from outside
Sensitive dataPrevent reading/modifying
Implementation detailsSafe to refactor
InvariantsEnforce 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:

FeaturePrivate #Symbol
Truly private
Reflection
Inherited
Syntax#nameSymbol()
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 caseExample
Constantsstatic PI = 3.14
Factory methodsstatic from(json)
Countersstatic instances = 0
Cached datastatic cache = new Map()
Utility methodsstatic parse(str)
Singletonsstatic get instance()
Registrystatic #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

TypeSyntaxAccessible
Public instancefield = valueAnywhere via obj.field
Private instance#field = valueOnly inside class
Public staticstatic field = valueVia Class.field
Private staticstatic #field = valueOnly inside class
Public methodmethod() {}Anywhere
Private method#method() {}Only inside class
Static methodstatic method() {}Via Class.method()

Public Fields

FeatureMeaning
Per instanceEach instance gets its own
Initialized before constructorOrder matters
this availableCan reference earlier fields
Arrow functionsBind this to instance

Private Fields

FeatureMeaning
# prefixPart of the name
Truly privateNot accessible outside
Not enumerableHidden from Object.keys
Not inheritedSubclasses don’t see parent privates
Brand check#field in obj
No dynamic addMust be declared

Static Fields

FeatureMeaning
On the classNot on instances
InheritedSubclasses see them
Private allowedstatic #x
Blocks allowedstatic { ... }
this in methodsRefers to calling class

Static Blocks

FeatureMeaning
Runs onceWhen class is defined
Can use thisRefers to class
Order mattersInterleaved with fields
MultipleAllowed, run in order

Public vs Private vs Static

FeaturePublicPrivateStatic
Per instance
On class
Outside accessVia class
Reflection
Inheritance
# prefixOptional

Field Initialization Order

StepWhat runs
1Static fields and blocks
2Instance field initializers
3Constructor body

Comparison with Other Languages

FeatureJavaScriptTypeScriptJava
Private#xprivate 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

PitfallProblemSolution
Private inheritedSyntaxErrorUse protected getters
Field orderUndefined valuesDeclare dependencies first
Static on instanceundefinedUse this.constructor
Dynamic #fieldNot accessibleOnly #field in obj
Private in JSONSkippedProvide a toJSON()
this in staticConfusingIt’s the calling class
Multiple static blocks orderMisunderstandingRuns top-to-bottom
Arrow fields vs methodsDifferent thisArrow 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

ConceptSyntaxExample
Public fieldfield = valuecount = 0
Private field#field = value#balance = 0
Public staticstatic field = valuestatic version = '1.0'
Private staticstatic #field = valuestatic #instances = 0
Public methodmethod() {}increment() {}
Private method#method() {}#hash() {}
Static methodstatic method() {}static square(n) {}
Static blockstatic { ... }Init code
Brand check#field in objToken.is(obj)
Arrow fieldfield = () => {}Bound this

Key takeaways:

  • Class fields declare properties directly in the class body
  • Public fields are per-instance and initialized before the constructor
  • #private fields 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 obj is 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(), and this refers 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!