Chapter 24 of 28 · JavaScript for Beginners — Complete Free Tutorial
Classes & OOP in JavaScript
When data and behaviour belong together — a Task that knows how to complete itself, a Cart that computes its total — classes give you a clean blueprint. JavaScript's class syntax (ES6) runs on a prototype-based engine underneath; this chapter teaches the modern syntax, inheritance, encapsulation, and when not to use classes.
The class blueprint
class Task {
title = 'Untitled'; // field (public)
constructor(title, done = false) {
this.title = title; // 'this' = the new instance
this.done = done;
}
complete() { // method (shared on the prototype)
this.done = true;
return `${this.title} ✓`;
}
toString() {
return `${this.title}${this.done ? ' ✓' : ''}`;
}
}
const t = new Task('Ship release');
console.log(t.complete()); // → Ship release ✓
console.log(String(t)); // → Ship release ✓ (calls toString)
new creates the object, runs constructor, returns the instance. Methods live on the prototype — every instance shares one copy.
Getters and setters
class Temperature {
#celsius = 0; // private field (see below)
constructor(c) { this.#celsius = c; }
get fahrenheit() {
return this.#celsius * 9 / 5 + 32;
}
set fahrenheit(f) {
this.#celsius = (f - 32) * 5 / 9;
}
}
const room = new Temperature(20);
console.log(room.fahrenheit); // → 68 (property access, not a method call)
room.fahrenheit = 100;
console.log(room.fahrenheit); // → 212 (setter updated the private field)
Getters/setters let you validate or compute on access while consumers use plain property syntax.
Private fields: real encapsulation
class BankAccount {
#balance = 0; // hard privacy — even 'this' outside cannot touch it
deposit(amount) {
if (amount <= 0) throw new RangeError('Amount must be positive');
this.#balance += amount;
return this.#balance;
}
get balance() { return this.#balance; }
}
const acc = new BankAccount();
acc.deposit(100);
console.log(acc.balance); // → 100
acc.#balance; // ❌ SyntaxError: Private field
Expected output: the balance can change only through validated methods — encapsulation, the first pillar of OOP, enforced by the language. (The older _underscore convention is only a gentleman's agreement; # is real.)
Static members
class Utils {
static clamp(n, min, max) { return Math.min(max, Math.max(min, n)); }
static VERSION = '1.2.0';
}
console.log(Utils.clamp(15, 0, 10)); // → 10 (no instance needed)
Static methods belong to the class itself — factories (User.create()), helpers, constants. They do not have instance this.
Inheritance: extends and super
class Animal {
#name;
constructor(name) { this.#name = name; }
speak() { return `${this.#name} makes a sound`; }
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // MUST run before using 'this'
this.breed = breed;
}
speak() { // overriding
return `${this.constructor.name} barks`;
}
}
const d = new Dog('Rex', 'Beagle');
console.log(d.speak()); // → Dog barks
console.log(d instanceof Dog); // → true
console.log(d instanceof Animal); // → true
extendssets the prototype chain;super()calls the parent constructor (required beforethis).- Override a method simply by redefining it; call the parent version with
super.method(). instanceofwalks the whole chain.
Composition over inheritance
// inheritance: "is-a" — fine when the relationship is true
class Admin extends User { … }
// composition: "has-a" — usually more flexible
class Task {
constructor(title, assignee = null) {
this.title = title;
this.assignee = assignee; // a User object, not extends User
}
}
Favor small classes collaborating via composition; deep inheritance trees are hard to change. Inheritance fits clear is-a hierarchies (two or three levels deep at most).
Where classes shine (and where they don't)
| Good fits | Better with plain functions/objects |
|---|---|
Domain models: Invoice, Game, Matrix | Data containers (use objects) |
Stateful services with invariants: Queue, Store | One-off transforms (use functions) |
| Error types, event emitters, resource wrappers | Simple configs and options |
Things needing instanceof | Callbacks and utilities |
A brief word on prototypes
function LegacyPoint(x, y) { this.x = x; this.y = y; } // pre-ES6 style
LegacyPoint.prototype.dist = function () { return Math.hypot(this.x, this.y); };
const p = new LegacyPoint(3, 4);
console.log(p.dist()); // → 5
Classes are sugar over exactly this: the constructor function, Prototype.prototype, and [[Prototype]] chains (which also power extends). You do not need the old syntax, but understanding prototypes explains how methods are shared and why this works.
Common mistakes
- Forgetting
new— calling a class without it throwsTypeError(unlike old constructor functions). - Using
thisbeforesuper()—ReferenceError. - Arrow functions as class methods — they are not prototype methods (
class A { fn = () => {} }creates a per-instance field — sometimes intentional for callbacks, but different semantics). - Everything-is-a-class — over-engineering simple data; use the table above.
- Forgetting that methods are shared — never mutate a method on one instance expecting others to change.
- Public mutable state — add validation via methods/getters instead of letting outside code do
obj.x = anything.
Practice checklist
- Model a
TodoListclass: private array,add/remove/countmethods, getters. - Create
ValidationError extends Errorand catch it by name (used later in real projects constantly). - Build a small
GamewithPlayer extends Entity, overridingact(). - Refactor a “god function” into 2–3 collaborating classes/functions and compare readability.
Your architecture is taking shape — but where does data survive a page reload? Next: Browser Storage & Web APIs.
Frequently Asked Questions
JavaScript is the programming language of the web. It adds behaviour to pages: responding to clicks and input, validating forms, fetching data from APIs, updating the DOM without reloading, and much more. It also runs on servers (Node.js), in desktop and mobile apps, and in build tooling.
They are unrelated languages that share a similar name for historical marketing reasons. Java is a statically-typed compiled language; JavaScript is a dynamically-typed interpreted language designed for the browser. Knowing one does not imply knowledge of the other.
Helpful but not mandatory — HTML gives you the structure JavaScript manipulates and CSS the styling it toggles. If you know basic tags, classes and selectors, you have enough to start; the DOM chapters assume only that much.
Use const by default and let when the value must change. Avoid var entirely in modern code: it is function-scoped instead of block-scoped, hoists differently, and can create accidental globals. This course uses const and let throughout.
The Document Object Model is the browser’s in-memory tree of objects representing your HTML. JavaScript reads and modifies that tree — selecting elements, changing text, adding nodes, attaching event listeners — and the browser re-renders the page to match.
== compares after converting types (so 5 == "5" is true), while === requires the same value AND the same type (5 === "5" is false). Loose equality has famously confusing rules — use === and !== everywhere unless you have a specific reason not to.
JavaScript is single-threaded, so long-running work (timers, fetches, events) is handled by the browser and its callbacks are queued. The event loop moves queued callbacks onto the call stack only when it is empty, with promise callbacks (microtasks) taking priority over timers. It is why asynchronous code never freezes the page.
A Promise is an object representing a future value — pending, fulfilled or rejected — that can be chained and combined (Promise.all). async/await is syntax sugar over promises that lets asynchronous code read top-to-bottom with ordinary try/catch error handling.
Yes. Frameworks are built on exactly these fundamentals — components are functions, state is your variable discipline, effects are events, rendering is DOM work. Developers who learn vanilla first understand what the framework is doing and debug far faster.
Type out the examples instead of copying, build small end-to-end projects (expense tracker, weather app, quiz), handle every UI state (loading, empty, success, error), and revisit fundamentals while reading real code. Consistency beats volume — a little most days outpaces occasional marathons.