JavaScript - Classes (OOP)
Overview
Estimated time: 35–45 minutes
Classes provide a familiar syntax over JavaScript's prototype-based inheritance. Learn class fields, methods, getters/setters, static and private fields, and how to extend classes with extends
/super
.
Learning Objectives
- Declare classes with constructors, instance fields, and methods.
- Use getters/setters for computed or validated properties.
- Define static members and private fields with
#
. - Extend classes using
extends
and callsuper
correctly.
Prerequisites
Class basics
class Person {
// public field (per-instance)
role = 'user';
constructor(name) {
this.name = name; // creates a property on the instance
}
// prototype method (shared across instances)
greet() { return `Hi, I'm ${this.name}`; }
// getter/setter around a backing field
get upperName() { return String(this.name).toUpperCase(); }
set upperName(v) { this.name = String(v).toLowerCase(); }
// static method (on the class, not instances)
static isPerson(x) { return x instanceof Person; }
}
const p = new Person('Ada');
p.greet(); // "Hi, I'm Ada"
Person.isPerson(p); // true
Private fields and methods
Private identifiers start with #
and are only accessible within the class body.
class Counter {
#count = 0; // private field
#bump() { this.#count++; } // private method
inc() { this.#bump(); }
value() { return this.#count; }
}
const c = new Counter();
c.inc();
c.value(); // 1
// c.#count; // SyntaxError: private field is not accessible
Inheritance with extends and super
class Employee extends Person {
constructor(name, id) {
super(name); // call base constructor first
this.id = id;
}
// override method
greet() { return `${super.greet()} (Employee #${this.id})`; }
}
new Employee('Bob', 7).greet();
// "Hi, I'm Bob (Employee #7)"
this binding and class fields
Extracted methods lose their this
binding. Use Function.prototype.bind
or a public field arrow function to capture this
per instance.
class Button {
label = 'Click';
handleClick() { console.log('clicked', this.label); }
handleClickBound = () => { console.log('clicked', this.label); } // captures this
}
const b = new Button();
const f1 = b.handleClick;
const f2 = b.handleClickBound;
try { f1(); } catch (e) { /* this is undefined in strict mode */ }
f2(); // works; logs 'clicked Click'
Common Pitfalls
- this binding: Passing methods as callbacks drops
this
. Use.bind(this)
or arrow function fields. - Initialization order: Field initializers run after
super()
in derived classes. - Privacy:
#private
is lexical and not accessible outside the class—even in tests or subclasses. - Class hoisting: Class declarations are not hoisted like functions; you must declare before use.
Try it
Run to exercise fields, private members, inheritance, and this-binding: