JavaScript Classes Table

PieceWhat it doesField note
class User {Class declarationClass bodies are strict mode by default - no sloppiness inside
constructor(props)Runs on newReturning an object from it replaces this - rarely wanted
method() {}Prototype methodLives on the prototype, shared across instances - not per-instance
#private = 1True private fieldThe hash is the syntax - enforced by the engine, not convention
static create()Class-level methodCalled on the class, not instances - factories live here
get value() / set value(v)Accessor pairProperty syntax with method logic behind it
extends BaseInheritancethis is uninitialized until super() runs - the classic first-line error
super.method()Parent callRequired in constructors before touching this
instanceof BaseType checkWalks the prototype chain; fails across realms
Reference: the MDN classes guide. Under the syntax, classes are prototypes with ceremony: methods live on the prototype (shared), fields live per-instance, and extends wires the prototype chain. Two rules prevent most class bugs: super() before any this in a subclass constructor, and remembering that class code is always strict mode - silent failures become loud ones. Bottom line: use classes for shapes with behavior; use plain objects for data - the Map vs Object decision has a class-shaped cousin. Related tools: Map and Set table, JS error table (custom error classes extend Error), and generators table (class syntax works there too).

ES classes gave JavaScript a clean object-oriented surface: declaration, constructor, methods, inheritance, private state. Under the surface it is still prototypes - but the syntax hides the machinery well enough that you only need to know it when debugging. The table below is the working nine: every piece of a real class, with the rule attached.

Bottom line: the two bugs everyone hits are both ordering errors. In a subclass, this does not exist until super() runs - touching this on the constructor's first line is the ReferenceError every JavaScript developer meets once. And the extends/super pair must appear together: extends without a super() call produces a constructor that cannot construct.

The honest part: class bodies are strict mode always, methods live on the prototype (shared, memory-efficient, invisible on instances), and true privacy finally arrived with #fields - enforced by the engine, unlike the underscore convention. The deeper design question mirrors the Map-versus-Object rule: classes for shapes with behavior, plain objects for data that gets serialized.

How to use

  1. Find the piece you need in the table; each row carries the rule that prevents its classic bug.
  2. Order the constructor correctly in subclasses: super() first, then any this access - no exceptions.
  3. Choose privacy honestly: #fields for state the compiler should enforce, underscore for conventions the teampolices.

Frequently asked questions

Why does 'this' not work in my subclass constructor?

Because in a derived class, this is uninitialized until super() executes - the language refuses to let you touch it first, throwing 'Must call super constructor before accessing this'. The fix is mechanical: super(...) must be the first statement, with whatever arguments the parent constructor needs. The error looks aggressive but it is protecting you from a half-built object: without the parent's initialization, this has no fields to read.

What is the difference between a method and a field in a class?

Methods written normally (describe() {}) live ON THE PROTOTYPE - one copy shared by every instance, invisible in Object.keys of the instance. Fields written as assignments (count = 0) live PER-INSTANCE - every object carries its own copy. The practical consequence: prototype methods are memory-efficient and cannot be enumerated over instances, while fields are data. Mixing them up shows up when iterating instances expecting to find methods, or when comparing two instances that share methods but diverge in fields.

How do private #fields actually work?

The hash makes privacy an engine guarantee, not a naming convention: this.#secret is inaccessible from outside the class body, period - no workaround, no prototype trick. Two gotchas worth knowing: the field must be DECLARED in the class body (even as #secret;) before assignment in some engine paths, and a missing hash on access throws a TypeError that looks like an undefined-property bug. Underscore-prefixed properties remain a team convention - anyone can still read them.

When should I use a class versus a plain object?

Classes earn their keep when state and behavior travel together with invariants to protect - a Validator that cannot be used before init, a Connection with a close() contract. Plain objects win for data that crosses boundaries: anything that will be JSON-serialized, passed between modules, or stored - serialization of class instances loses methods and needs revival logic. The corollary of the Map-versus-Object rule: shape with behavior is a class; bag of values is an object literal.

Related tools