JavaScript Classes
Build objects with class syntax covering constructors, inheritance, static members, and private fields.
TL;DR
- Define object blueprints with
classandconstructorsyntax. - Inherit shared behavior using
extendsandsuper()calls. - Hide internal state with private fields marked by
#.
Class Basics
class + constructorDefine a blueprint for creating objects with shared methods.
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}new keywordCreate instances with new, which runs the constructor automatically.
const user = new User('Ana', 'ana@example.com');
console.log(user.name); // "Ana"Instance methodsDefine instance methods inside the class body without the function keyword.
class User {
constructor(name) { this.name = name; }
greet() {
return `Hi, ${this.name}`;
}
}Shared prototypeMethods live on the prototype, so instances share a single function reference.
const a = new User('Ana'), b = new User('Leo');
console.log(a.greet === b.greet); // trueStatic Methods and Properties
static methodAttach utility functions directly to the class instead of its instances.
class MathHelper {
static square(n) { return n * n; }
}
console.log(MathHelper.square(4)); // 16static fieldHold class-level shared state across all created instances.
class Counter {
static count = 0;
constructor() { Counter.count++; }
}
new Counter(); new Counter();
console.log(Counter.count); // 2Factory methodReturn pre-configured instances using static factory functions.
class User {
static fromJSON(json) {
const { name, email } = JSON.parse(json);
return new User(name, email);
}
}Getters, Setters, and Private Fields
getDefine an accessor property that computes its value on read.
class Circle {
constructor(radius) { this.radius = radius; }
get area() { return Math.PI * this.radius ** 2; }
}setIntercept assignments to validate or transform incoming values.
class Circle {
set radius(val) {
if (val <= 0) throw new RangeError('Must be > 0');
this._radius = val;
}
}Private fields (#)Prefix with # to encapsulate internal state from external access.
class BankAccount {
#balance = 0;
deposit(n) { this.#balance += n; }
get balance() { return this.#balance; }
}Enforced privacyAccessing private fields outside the class throws a SyntaxError.
const acc = new BankAccount();
acc.#balance; // SyntaxError: Private fieldInheritance with Extends and Super
extendsCreate a subclass that inherits methods and properties from a parent.
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} speaks`; }
}
class Dog extends Animal {}super()Call super() in subclass constructors before accessing this.
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
}super.method()Extend parent behavior without rewriting inherited logic.
class Dog extends Animal {
speak() {
return `${super.speak()} loudly`;
}
}instanceofConfirm an object belongs to a class or inherits from its prototype chain.
const rex = new Dog('Rex', 'Lab');
console.log(rex instanceof Animal); // trueClasses vs Prototypes
Syntactic sugarClasses provide cleaner syntax over JavaScript's prototype inheritance.
class Point {
constructor(x, y) { this.x = x; this.y = y; }
}Methods on prototypeClass methods are placed directly onto the constructor's prototype.
class Point {
dist() { return Math.hypot(this.x, this.y); }
}
console.log(typeof Point.prototype.dist); // "function"typeof a classThe typeof operator evaluates class declarations as functions.
console.log(typeof Point); // "function"Requires newInvoking a class constructor without new throws a TypeError.
class NewStyle {}
NewStyle(); // TypeError: Must be invoked with 'new'Tips
- Use private fields with a hash prefix to stop outside code from reading or changing internal state directly.
- Call super() before using this in a subclass constructor, since the parent must initialize the instance first.
- Prefer static methods for utility functions that relate to a class but don't need a specific instance.
Warnings
- Forgetting to call super() in a subclass constructor throws a ReferenceError before this can be accessed.
- Arrow function class fields capture this permanently, which can surprise developers expecting normal method binding rules.
- Class declarations are not hoisted like functions, so using a class before its definition throws an error.
In Practice
Subclasses extend base classes with super(), encapsulate balance via private fields, and safely apply interest.
- #balance stays private, accessible only through the public getter.
- SavingsAccount extends Account and delegates setup via super().
- applyInterest() calculates rate against balance and deposits the result.
class Account {
#balance;
constructor(owner, balance = 0) {
this.owner = owner;
this.#balance = balance;
}
deposit(amount) { return (this.#balance += amount); }
get balance() { return this.#balance; }
}
class SavingsAccount extends Account {
constructor(owner, balance, rate) {
super(owner, balance);
this.rate = rate;
}
applyInterest() {
return this.deposit(this.balance * this.rate);
}
}
const savings = new SavingsAccount('Priya', 1000, 0.05);
savings.applyInterest();
console.log(savings.balance); // 1050
console.log(savings instanceof Account); // trueFAQ
A class field declares a property directly on the class body, and it runs before the constructor body executes. A constructor assignment sets the property inside the constructor function instead. Both end up creating the same instance property, but fields are often shorter for simple defaults.
Yes, classes compile down to the same prototype-based inheritance JavaScript always used. Methods defined in a class body are added to the prototype, not to each instance. Classes simply give that pattern cleaner, more familiar syntax.
An underscore prefix like _name is just a convention; outside code can still access it. A true private field written as #name is enforced by the engine. Accessing it from outside the class throws an error, so private fields offer real encapsulation, not just a hint.
Use a static method when the logic doesn't depend on a specific instance, like a factory function or a helper. Static methods are called on the class itself, such as MyClass.create(). Instance methods need this to refer to specific object data.