Chapter 23

Under the Hood of class: ClassDefinitionEvaluation and Private Fields

class is syntactic sugar, but not simple syntactic sugar. ClassDefinitionEvaluation does far more than function + prototype handwriting can replicate when it comes to setting up the prototype chain, registering methods, and handling private fields. Private fields in particular operate entirely outside the prototype chain.

๐Ÿ”น Level 1 ยท What You Need to Know

The Basic Structure of a Class

class Animal {
  #name  // private field declaration (must appear at the top of the class body)
  
  constructor(name) {
    this.#name = name   // private field assignment
  }
  
  speak() {            // instance method (defined on the prototype)
    return `${this.#name} makes a sound`
  }
  
  static create(name) { // static method (defined on the class itself)
    return new Animal(name)
  }
  
  get name() {         // getter (defined on the prototype)
    return this.#name
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name)        // must be called before using 'this'!
    this.type = 'dog'
  }
  
  speak() {
    return `${this.name} barks`  // access private field via getter
  }
}

const d = new Dog('Rex')
console.log(d.speak())            // 'Rex barks'
console.log(d instanceof Dog)     // true
console.log(d instanceof Animal)  // true

Where Methods Are Defined

Syntax Defined On Enumerable
method() {} ClassName.prototype false
static method() {} ClassName false
get prop() {} ClassName.prototype false
#privateMethod() {} Each instance object โ€”

Core Characteristics of Private Fields

#field is truly private, not a convention like _field:

class Secret {
  #value = 42
  
  reveal() { return this.#value }
}

const s = new Secret()
console.log(s.reveal())     // 42
console.log(s.#value)       // SyntaxError: Private field '#value' must be declared in an enclosing class
console.log(s['#value'])    // undefined (this is an ordinary property, not a private field)
console.log('#value' in s)  // false (regular 'in' cannot see private fields)

Why super() Must Come Before this

A subclass's this is created and initialized by super(). Using this before calling super() throws a ReferenceError:

class Parent {
  constructor() { this.x = 1 }
}

class Child extends Parent {
  constructor() {
    // console.log(this)  // ReferenceError: Must call super constructor before accessing 'this'
    super()
    console.log(this)  // Parent { x: 1 } โ€” this has been initialized by super()
    this.y = 2
  }
}

๐Ÿ”ธ Level 2 ยท How It Actually Works

The Complete Prototype Structure of class Inheritance

class Animal { ... }
class Dog extends Animal { ... }

Complete prototype chain structure:

  Dog (function object)              Animal (function object)
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”               โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚ [[Prototype]] โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ (Animal fn)     โ”‚
  โ”‚                 โ”‚               โ”‚                 โ”‚
  โ”‚ prototype โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”            โ”‚ prototype โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
                       โ”‚                                 โ”‚
                       โ–ผ                                 โ–ผ
  Dog.prototype         โ”‚            Animal.prototype    โ”‚
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
  โ”‚ [[Prototype]] โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ [[Prototype]] โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ–บ Object.prototype
  โ”‚ constructor โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ–บ Dog     โ”‚ constructor โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ–บ Animal
  โ”‚ speak()         โ”‚  โ”‚            โ”‚ speak()         โ”‚  โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
          โ–ฒ            โ”‚                    โ–ฒ            โ”‚
          โ”‚            โ”‚                    โ”‚            โ”‚
  new Dog() instance   โ”‚            new Animal() instanceโ”‚
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
  โ”‚ [[Prototype]] โ”€โ”€โ”‚โ”€โ”€โ”˜            โ”‚ [[Prototype]] โ”€โ”€โ”‚โ”€โ”€โ”˜
  โ”‚ type: 'dog'     โ”‚               โ”‚ (own properties) โ”‚
  โ”‚ #name (private) โ”‚               โ”‚ #name (private) โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜               โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Two prototype chains:
  1. Instance chain: instance โ†’ Dog.prototype โ†’ Animal.prototype โ†’ Object.prototype
  2. Constructor chain: Dog โ†’ Animal โ†’ Function.prototype
     (this allows static methods to be inherited)

ClassDefinitionEvaluation Step by Step

Spec 15.7.14 ClassDefinitionEvaluation is the core algorithm invoked when a class declaration or expression is evaluated. Simplified steps:

ClassDefinitionEvaluation(classBinding, classHeritage, classBody):

Step 1: Create class environment
  - Create ClassEnvironment (scope for private names)
  - If classBinding present, bind class name in environment
    (the name of a class expression is visible inside the body)

Step 2: Handle extends (if present)
  - Evaluate classHeritage โ†’ superclass
  - Verify superclass is a function or null
  - Determine protoParent (= superclass.prototype or null)
  - Determine constructorParent (= superclass)

Step 3: Create the prototype object
  - proto = OrdinaryObjectCreate(protoParent)
    i.e., proto.[[Prototype]] = protoParent (superclass's prototype)

Step 4: Initialize the constructor
  - If classBody has explicit constructor: use it
  - If not:
    - Base class: use default constructor() {}
    - Derived class: use default constructor(...args) { super(...args) }
  - Create function object F via OrdinaryFunctionCreate
  - Set F.prototype = proto
  - Set proto.constructor = F (non-enumerable)
  - If extends present:
    - F.[[Prototype]] = superclass (constructor inheritance)
    - F.[[ConstructorKind]] = 'derived'
  - Otherwise:
    - F.[[Prototype]] = Function.prototype
    - F.[[ConstructorKind]] = 'base'

Step 5: Process each ClassElement
  For each element in classBody:
  - Instance methods: PropertyDefinitionEvaluation โ†’ defined on proto (enumerable: false)
  - Static methods: PropertyDefinitionEvaluation โ†’ defined on F (enumerable: false)
  - Private methods: registered in PrivateEnvironment
  - Instance fields (including private): collected into fields list
  - Static fields: evaluated and defined on F immediately

Step 6: Install private methods
  - Associate each private method's PrivateName with the method in PrivateEnvironment

Step 7: Initialize static fields
  - Evaluate each static field initializer in declaration order

Step 8: Return constructor F

How Private Fields Are Implemented

Private fields are not prototype properties or ordinary properties. They are implemented through PrivateName + [[PrivateElements]]:

Private Field Implementation Structure:

ClassEnvironment (created when class is evaluated):
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚ PrivateEnvironmentRecord                   โ”‚
  โ”‚   #name  โ†’ PrivateName { [[Description]]: '#name' }  โ”‚
  โ”‚   #count โ†’ PrivateName { [[Description]]: '#count' } โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
          โ”‚
          โ”‚ when each instance is created (new ClassName())
          โ–ผ
Instance object (OrdinaryObject):
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚ [[PrivateElements]]                        โ”‚
  โ”‚   [ { [[Key]]:   PrivateName(#name),       โ”‚
  โ”‚         [[Kind]]:  'field',                โ”‚
  โ”‚         [[Value]]: 'Rex' },                โ”‚
  โ”‚     { [[Key]]:   PrivateName(#count),      โ”‚
  โ”‚         [[Kind]]:  'field',                โ”‚
  โ”‚         [[Value]]: 0 } ]                   โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Accessing obj.#name:
  1. Linear search in obj.[[PrivateElements]] for [[Key]] === PrivateName(#name)
  2. If found: return [[Value]]
  3. If not found: throw TypeError
     "Cannot read private member #name from an object whose class did not declare it"
class Dog {
  #name
  
  constructor(name) { this.#name = name }
  
  static isInstance(obj) {
    return #name in obj  // detect private field via 'in' operator (ES2022)
  }
}

class Cat {
  #name
  constructor(name) { this.#name = name }
}

const dog = new Dog('Rex')
const cat = new Cat('Whiskers')

console.log(Dog.isInstance(dog))  // true
console.log(Dog.isInstance(cat))  // false โ€” Cat's #name and Dog's #name are different PrivateName objects

Where Private Methods Are Stored

Private methods differ from public methods: public methods are defined on the prototype (shared by all instances), while private method entries exist in each instance's [[PrivateElements]] (though the function object itself is shared):

Public Method vs Private Method Storage:

Public method speak():
  Dog.prototype.speak = function() { ... }
  All instances share the same function object via prototype chain
  Memory: O(1), independent of instance count

Private method #speak():
  Each instance's [[PrivateElements]] contains one entry:
  { [[Key]]: PrivateName(#speak), [[Kind]]: 'method', [[Value]]: <function> }
  Although [[Value]] points to the same function object,
  each instance carries its own PrivateElement entry
  Memory: O(n), proportional to instance count (each entry has overhead)
class WithPrivateMethod {
  #secret() { return 42 }
  publicCall() { return this.#secret() }
}

const obj = new WithPrivateMethod()
console.log(obj.publicCall())  // 42
console.log(obj['#secret'])    // undefined (not accessible this way)
// obj.#secret()               // SyntaxError (#secret not in current lexical scope)

The TDZ of class Declarations

class declarations, like let/const, have a Temporal Dead Zone โ€” the declaration is hoisted but cannot be accessed until the declaration statement is executed:

class Declaration Hoisting and TDZ:

Before execution:
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚ Variable Environment                 โ”‚
  โ”‚   Animal โ†’ <uninitialized> (TDZ)    โ”‚
  โ”‚   Dog    โ†’ <uninitialized> (TDZ)    โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

After executing class Animal { ... }:
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚ Variable Environment                 โ”‚
  โ”‚   Animal โ†’ [Animal function object] โ”‚
  โ”‚   Dog    โ†’ <uninitialized> (TDZ)    โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
// class declarations have TDZ โ€” cannot use before declaration
new MyClass()  // ReferenceError: Cannot access 'MyClass' before initialization
class MyClass {}

// function declarations are fully hoisted โ€” can use before declaration
new MyFunc()   // works fine
function MyFunc() {}

๐Ÿ”บ Level 3 ยท What the Spec Says

15.7 Class Definitions

Spec 15.7 defines the evaluation of class-related syntax. Class declaration runtime semantics reference 15.7.14 ClassDefinitionEvaluation. Key algorithm nodes:

ClassElement Classification (15.7.1):

ClassElement types:
  MethodDefinition            โ†’ instance method
  static MethodDefinition     โ†’ static method
  FieldDefinition ;           โ†’ instance field
  static FieldDefinition ;    โ†’ static field
  ClassStaticBlock            โ†’ static initialization block (ES2022)
  ;                           โ†’ empty element (ignored)

When Class Fields Are Initialized:

Instance fields (#field = expr, field = expr) are not initialized during ClassDefinitionEvaluation. They are collected as initializers (InitializeInstanceElements) and executed on each new call during [[Construct]]:

Extra steps in [[Construct]] related to classes:

1. Create new object thisArgument
2. Call InitializeInstanceElements(thisArgument, F):
   a. Install private methods: for each method in F.[[PrivateMethods]],
      add a PrivateElement entry to thisArgument.[[PrivateElements]]
   b. Execute field initializers: for each field in declaration order
      - Private field #f = expr โ†’ add PrivateName โ†’ value to [[PrivateElements]]
      - Public field f = expr  โ†’ DefinePropertyOrThrow(thisArgument, 'f', ...)
3. Execute constructor body

PrivateEnvironment Records (Spec 9.2)

PrivateEnvironmentRecord is a new type of environment record (introduced in ES2022) dedicated to managing the scope of private names:

PrivateEnvironmentRecord structure:

{
  [[OuterPrivateEnvironment]]: outer PrivateEnvironmentRecord or null,
  [[Names]]: list of private names (PrivateName objects)
}

PrivateName object:
{
  [[Description]]: '#fieldName' (used in error messages)
}

Note: PrivateName is an object reference โ€” identity comparison (===) checks
reference equality. Dog's #name and Cat's #name are completely different
PrivateName objects, even if their description strings are identical.
This guarantees cross-class access security.

[[PrivateElements]] Storage and Access

Each object instance's [[PrivateElements]] is a List where each entry is a PrivateElement Record:

PrivateElement Record:
{
  [[Key]]:   PrivateName (a reference, not a string)
  [[Kind]]:  'field' | 'method' | 'accessor'
  [[Value]]: field value / method function object
             (accessor kind has [[Get]] and [[Set]] instead of [[Value]])
}

The private field access operation PrivateGet(P, O) (spec 7.3.26):

PrivateGet(P, O):
  // P is a PrivateName, O is an object

1. entry = PrivateElementFind(O, P)
   // linear search in O.[[PrivateElements]] for [[Key]] === P

2. If entry is undefined:
     throw TypeError ("Cannot read private member of an object
                       whose class did not declare it")

3. If entry.[[Kind]] === 'field':
     return entry.[[Value]]

4. If entry.[[Kind]] === 'method':
     return entry.[[Value]] (return the method function directly)

5. If entry.[[Kind]] === 'accessor':
     if entry.[[Get]] is undefined: throw TypeError
     return Call(entry.[[Get]], O)

The private field write operation PrivateSet(P, O, value) (spec 7.3.27):

PrivateSet(P, O, value):

1. entry = PrivateElementFind(O, P)

2. If entry is undefined: throw TypeError

3. If entry.[[Kind]] === 'field':
     entry.[[Value]] = value
     
4. If entry.[[Kind]] === 'method':
     throw TypeError (private methods cannot be assigned)
     
5. If entry.[[Kind]] === 'accessor':
     if entry.[[Set]] is undefined: throw TypeError
     Call(entry.[[Set]], O, [value])

The in Operator for Private Fields (Spec 13.10.1)

ES2022 introduced private field in detection (Ergonomic brand checks):

HasPrivateName(O, P):
  // Implementation of `#field in obj`

1. entry = PrivateElementFind(O, P)
2. If entry is not undefined: return true
3. return false

This is more precise than instanceof: instanceof checks the prototype chain and can fail with multiple Realms or a modified Symbol.hasInstance; #field in obj directly checks [[PrivateElements]] and is unaffected by the prototype chain.


๐Ÿ’Ž Level 4 ยท Edge Cases and Traps

Trap 1: The this Initialization Rule in Subclass Constructors

A subclass's ([[ConstructorKind]] === 'derived') this is created by the parent class's [[Construct]], not the subclass. Before super() returns, this is in an "uninitialized" state:

class Base {
  constructor() {
    this.baseField = 1
  }
}

class Derived extends Base {
  #privateField

  constructor() {
    // accessing this here throws ReferenceError
    // console.log(this)  // ReferenceError

    super()
    // after super() returns, this has been initialized by Base's constructor
    // and InitializeInstanceElements has already installed private fields
    console.log(this.baseField)   // 1
    this.#privateField = 42       // private field is now accessible
  }
}

new Derived()

If a subclass omits the constructor entirely, the default constructor(...args) { super(...args) } is used, forwarding all arguments automatically.

Trap 2: Static Private Fields Are Inaccessible Even to Subclasses

class Parent {
  static #secret = 'parent secret'
  
  static getSecret() {
    return Parent.#secret  // correct: within Parent's lexical scope
  }
}

class Child extends Parent {
  static getChildSecret() {
    // return Child.#secret  // SyntaxError: #secret not declared in Child
    // Static private fields are NOT inherited!
    return Parent.getSecret()  // must access via parent's public method
  }
}

console.log(Parent.getSecret())      // 'parent secret'
console.log(Child.getChildSecret())  // 'parent secret'
// console.log(Child.#secret)        // SyntaxError

Trap 3: #field in obj Is a More Reliable Instance Check Than instanceof

class MyClass {
  #brand  // private field used as a brand check
  
  constructor() {
    this.#brand = true
  }
  
  static isInstance(obj) {
    return #brand in obj  // if the object has this private field, it's a MyClass instance
  }
}

// Problem with instanceof: it can be faked
class FakeClass {
  static [Symbol.hasInstance](obj) { return true }  // spoofing instanceof
}
console.log({} instanceof FakeClass)  // true โ€” false positive!

// Cross-realm: instanceof fails
// e.g., arrays from an iframe:
// [] instanceof Array  // false (different Realm's Array is a different constructor)

// #brand in obj advantages:
const obj = new MyClass()
console.log(MyClass.isInstance(obj))    // true
console.log(MyClass.isInstance({}))     // false
console.log(MyClass.isInstance(null))   // false (doesn't throw, returns false)

Trap 4: Class Methods Are Non-Enumerable; Object Literal Methods Are Enumerable

This difference causes surprises during serialization and for...in traversal:

class Foo {
  bar() {}
}

const obj = {
  bar() {}
}

// Check enumerability
const classDescriptor = Object.getOwnPropertyDescriptor(Foo.prototype, 'bar')
console.log(classDescriptor.enumerable)  // false

const objDescriptor = Object.getOwnPropertyDescriptor(obj, 'bar')
console.log(objDescriptor.enumerable)    // true

// Practical impact: for...in does not traverse class methods
for (const key in new Foo()) {
  console.log(key)  // no output (bar is non-enumerable)
}

for (const key in obj) {
  console.log(key)  // 'bar' (enumerable)
}

Trap 5: Private Method Memory Overhead Scales with Instance Count

class PublicMethod {
  method() { return 42 }
}

class PrivateMethod {
  #method() { return 42 }
  call() { return this.#method() }
}

// Create 10,000 instances
const publicInstances = Array.from({ length: 10000 }, () => new PublicMethod())
const privateInstances = Array.from({ length: 10000 }, () => new PrivateMethod())

// Public method: all instances share PublicMethod.prototype.method โ€” 1 function object
// Private method: each instance has one PrivateElement entry in [[PrivateElements]]
//   Although [[Value]] points to the same function object,
//   10,000 PrivateElement records themselves carry memory overhead

// In memory-sensitive scenarios, weigh the trade-off carefully
// Consider WeakMap-based privacy (the old approach) for high-volume instances

// WeakMap pattern (private-like, no per-instance overhead)
const _method = new WeakMap()
class WeakMapPrivate {
  constructor() {
    _method.set(this, function() { return 42 })
  }
  call() { return _method.get(this)() }
}

Chapter Summary

  1. ClassDefinitionEvaluation creates two prototype chains: the instance chain (instance โ†’ ClassName.prototype โ†’ ParentClass.prototype) and the constructor chain (ClassName โ†’ ParentClass), the latter enabling static method inheritance.
  2. Private fields are implemented via PrivateName + [[PrivateElements]]. PrivateName is an object reference (not a string); same-named private fields from different classes are completely different PrivateName objects and cannot access each other.
  3. A subclass's this is created by super() calling the parent's [[Construct]]. Accessing this before super() throws ReferenceError. Field initializers (including private fields) execute after super() returns, before the rest of the constructor body.
  4. Static private fields (static #field) are only visible within the lexical scope of the declaring class. Subclasses cannot inherit or access them, even through instances.
  5. Each instance of a class with private methods carries an independent PrivateElement entry in [[PrivateElements]], making memory cost O(n) with instance count. Evaluate the impact in scenarios with high-frequency instance creation.
Rate this chapter
4.8  / 5  (6 ratings)

๐Ÿ’ฌ Comments