Class Basics
The class syntax, introduced in ES2015, gives JavaScript a clean, familiar way to define blueprints for objects. A class bundles together the data each object holds and the behavior it can perform, so you can stamp out as many independent instances as you need from a single definition. It is the modern, idiomatic way to model “types” of things — users, points, HTTP requests — and it reads far more clearly than the constructor-function patterns that came before it.
Declaring a class
A class declaration starts with the class keyword followed by a capitalized name (by convention) and a body in braces. The body holds a special constructor method plus any number of instance methods. Nothing runs until you create an instance.
class User {
constructor(name, email) {
this.name = name;
this.email = email;
this.active = true;
}
greet() {
return `Hi, I'm ${this.name}`;
}
}
const alice = new User("Alice", "[email protected]");
console.log(alice.name);
console.log(alice.greet());
console.log(alice.active);
Output:
Alice
Hi, I'm Alice
true
Unlike function declarations, class declarations are not hoisted in a usable way. They live in the temporal dead zone, so referencing a class before its declaration throws a
ReferenceError. Define the class before you instantiate it.
The constructor
The constructor is a reserved method that runs automatically when you create an instance with new. Its job is to set up the object’s initial per-instance state by assigning to this. Each class can have at most one constructor; if you omit it, JavaScript supplies an empty default for you.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
const origin = new Point(0, 0);
const p = new Point(3, 4);
console.log(origin);
console.log(p.x, p.y);
Output:
Point { x: 0, y: 0 }
3 4
The constructor receives whatever arguments you pass to new, so it is the right place to validate input, apply defaults, and compute derived fields.
Instance methods
Methods declared in the class body — anything that isn’t the constructor — become instance methods. Every instance can call them, and inside a method this refers to the specific object it was called on. Crucially, these methods are defined once and shared across all instances rather than copied into each object.
class Counter {
constructor(start = 0) {
this.count = start;
}
increment() {
this.count += 1;
return this.count;
}
reset() {
this.count = 0;
return this.count;
}
}
const c = new Counter(5);
console.log(c.increment());
console.log(c.increment());
console.log(c.reset());
Output:
6
7
0
You can call one method from another through this, chain behavior, and return values just like ordinary functions.
Creating instances with new
The new keyword turns a class into a live object. It allocates a fresh object, links it to the class’s prototype, runs the constructor with this bound to that object, and returns the result. Every new call yields an independent instance.
class User {
constructor(name) {
this.name = name;
}
}
const a = new User("Eve");
const b = new User("Mallory");
console.log(a === b);
console.log(a.name, b.name);
console.log(a instanceof User);
Output:
false
Eve Mallory
true
Unlike constructor functions, classes require
new. CallingUser("Eve")without it throwsTypeError: Class constructor User cannot be invoked without 'new', which catches a common mistake at the call site.
Class expressions
Classes are values, so you can also define them with a class expression and assign the result to a variable. Expressions may be named or anonymous, which is handy when you build a class conditionally or return one from a function.
const Animal = class {
constructor(species) {
this.species = species;
}
describe() {
return `A ${this.species}`;
}
};
const cat = new Animal("cat");
console.log(cat.describe());
Output:
A cat
| Form | Syntax | Hoisted | Typical use |
|---|---|---|---|
| Declaration | class User { ... } | No (TDZ) | Most class definitions |
| Expression | const User = class { ... } | No | Conditional or returned classes |
Sugar over prototypes
A class is not a new kind of value — under the hood it is still a function, and its methods live on the prototype object exactly like the old constructor-function pattern. The class syntax simply automates that wiring and adds guardrails (it enforces new and makes methods non-enumerable).
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hi, ${this.name}`;
}
}
console.log(typeof User);
console.log(typeof User.prototype.greet);
const u = new User("Sam");
console.log(Object.getPrototypeOf(u) === User.prototype);
Output:
function
function
true
Recognizing that classes are prototype machinery in disguise explains their behavior: shared methods, instanceof checks, and inheritance all flow from the prototype chain you already know.
Best Practices
- Capitalize class names (
User,OrderService) to signal they are constructors meant fornew. - Keep the
constructorfocused on initializing state — validate inputs and set defaults, but avoid heavy work or side effects. - Store per-instance data on
this; let shared behavior live as instance methods on the prototype. - Always instantiate with
new; the class syntax will throw if you forget, so let it guide you. - Define a class before you use it, since class declarations are not hoisted into usable scope.
- Prefer
classover hand-written constructor functions in new code for clearer, safer, and more maintainable object definitions.