The class keyword in JavaScript is syntactic sugar over the prototype-based object model. It gives you a clean way to define constructors, methods, inheritance, and access modifiers. This lesson covers every feature you will use in real code, with attention to the parts that differ from other languages.
Your first class
A class is defined with the class keyword. The body usually contains a constructor and methods. The constructor runs when you create a new instance with new. Methods are added to the prototype, not to each instance, so they are shared.
class Dog {
constructor(name, breed) {
this.name = name;
this.breed = breed;
this.tricks = [];
}
teach(trick) {
this.tricks.push(trick);
}
showTricks() {
console.log(`${this.name} knows: ${this.tricks.join(", ") || "nothing yet"}`);
}
}
const rex = new Dog("Rex", "Labrador");
rex.teach("sit");
rex.teach("roll over");
rex.showTricks();
Public and private fields
Declare instance fields directly in the class body for a cleaner alternative to setting them in the constructor. Prefix a field with # to make it private. Private fields are truly inaccessible from outside the class, which is a stronger guarantee than underscore conventions.
class Counter {
#value = 0;
inc() { this.#value += 1; }
get value() { return this.#value; }
}
const c = new Counter();
c.inc(); c.inc(); c.inc();
console.log(c.value); // 3
// c.#value // SyntaxError: private field
Getters and setters
Use get and set to expose a method as a property. They run on every access, so they are perfect for computed attributes, validation, and side effects like logging.
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}
get fahrenheit() {
return this.celsius * 9 / 5 + 32;
}
set fahrenheit(value) {
this.celsius = (value - 32) * 5 / 9;
}
}
const t = new Temperature(100);
console.log(t.fahrenheit); // 212
t.fahrenheit = 32;
console.log(t.celsius); // 0
Static members on the class itself
Static methods and fields live on the class, not on instances. Use them for utilities that are conceptually attached to the class, factory methods, and constants.
class User {
static count = 0;
constructor(name) {
this.name = name;
User.count += 1;
}
static createGuest() {
return new User("Guest");
}
}
new User("Asha");
new User("Bala");
console.log(User.count); // 2
console.log(User.createGuest().name); // Guest
Inheritance with extends and super
A subclass extends a parent class and inherits its fields and methods. The constructor of the subclass must call super() before using this. Use super.method() to call a parent method that you are overriding.
class Animal {
constructor(name) {
this.name = name;
}
speak() { return "..."; }
}
class Dog extends Animal {
speak() { return "Woof"; }
fetch(item) { return `${this.name} fetches ${item}`; }
}
class Cat extends Animal {
speak() { return "Meow"; }
}
const rex = new Dog("Rex");
console.log(rex.speak()); // Woof
console.log(rex.fetch("ball")); // Rex fetches ball
Summary
Use classes to model entities with state and behaviour. Reach for private fields for true encapsulation, getters and setters for computed attributes, static members for utilities on the class, and extends for inheritance. The next lesson covers prototypes, the system under the hood that powers every class.
When to use classes versus plain objects
Classes are the right tool when you have many instances of the same shape and want to share behaviour. They are the wrong tool when you have a single config object, a one-off data record, or a tree of nested data. A user record pulled from an API is fine as a plain object; you will not call new on its shape, you will just read its fields. A counter, a logger, or a request handler is a good class candidate because you will create many of them and they share behaviour. Reach for plain objects for data; reach for classes for behaviour.
// Data: plain object
const user = { name: "Asha", role: "admin" };
// Behaviour: class
class Logger {
#prefix;
constructor(prefix) { this.#prefix = prefix; }
log(message) { console.log(`[${this.#prefix}] ${message}`); }
}
const logger = new Logger("INFO");
logger.log("ready");
Best practices for class design
Keep classes small and focused. A class should have one clear responsibility; if it grows past a screen or two, split it. Prefer composition over inheritance when the relationship between entities is "has a" rather than "is a": a car has an engine, so the Car class holds an Engine field instead of extending Engine. Use private fields for state that should not be touched from outside, and getters or setters when the access needs validation. Avoid deep inheritance chains; one or two levels is plenty. Prefer small interfaces and many small classes over large ones that try to do everything.
Comparison with Python
Python's class is similar to JavaScript's, but with differences in syntax and convention. Python uses __init__ instead of constructor; JavaScript uses constructor. Python uses self in methods; JavaScript uses this. Python's name-mangling with double underscore is roughly equivalent to JavaScript's #privateField, though Python's mangling is a convention the runtime enforces by renaming, while JavaScript's privacy is enforced by syntax. Python supports multiple inheritance with mixin classes and the MRO algorithm; JavaScript supports single inheritance through extends, and you get mixin behaviour by composing objects with Object.assign. Both languages support abstract base classes, though JavaScript does not have a built-in abc module and you usually write duck-typed interfaces instead.