Classes in JavaScript
A class in JavaScript is syntactic sugar over a constructor function and prototypes. From the outside it is a declarative description of an object type, while under the hood everything works exactly as before: methods land on prototype, and new creates a new object and binds this to it. Such code simply looks and reads much better.
Theory
TL;DR
classis syntactic sugar over a constructor function and prototypes, not a new inheritance model.constructor()runs onnew, and methods from the class body land onprototype, so they are shared by every instance.extendslinks the prototypes of two classes, andsuper()calls the parent constructor.staticmakes a method or property a member of the class itself rather than of an instance.#fields are private and reachable only inside the class body (ES2022), whileget/setcontrol how properties are read and written.- Classes are not hoisted the way functions are, and their methods are non-enumerable.
Quick example
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHi() {
console.log(`Hi, my name is ${this.name}`);
}
}
const user1 = new User('Alex', 25);
user1.sayHi(); // Hi, my name is Alexclassdeclares a class.constructor()is the special method invoked bynew User().thisis a reference to the object being created.- Class methods automatically end up on
prototype.
What it looks like under the hood
This code:
class User {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(this.name);
}
}is equivalent to the old ES5 code:
function User(name) {
this.name = name;
}
User.prototype.sayHi = function() {
console.log(this.name);
};The difference is not in the model but in the behaviour details: a class body always runs in strict mode, class methods are non-enumerable, so for...in over an instance will not see them, and a class cannot be called without new at all.
Parts of a class
| Feature | Description |
|---|---|
| constructor() | The method that runs when an object is created (new) |
| class methods | Added to prototype and available to every instance |
| extends | Inheritance from another class |
| super() | A call to the parent constructor |
| static | Static methods and properties that belong to the class itself |
private fields (#) | Fields that are unreachable outside the class |
| getters and setters | Let you control how properties are read and written |
A class can be declared not only as a statement but also as an expression (class expression):
const User = class {
sayHi() {
console.log('Hi!');
}
};
new User().sayHi(); // Hi!Or it can be given a name that is visible only inside the definition itself:
const User = class MyUser {
getClassName() {
return MyUser.name;
}
};Inheritance: extends and super
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // calls the parent constructor
this.breed = breed;
}
speak() {
console.log(`${this.name} barks!`);
}
}
const rex = new Dog('Rex', 'shepherd');
rex.speak(); // Rex barks!extendslinks the prototypes of the classes.super()calls the parentconstructor, and in a derived class it must run before the first use ofthis.- Methods can be overridden, just like in classic OOP.
static, private fields and accessors
Static methods and properties belong to the class itself, not to its instances:
class MathUtil {
static add(a, b) {
return a + b;
}
}
console.log(MathUtil.add(2, 3)); // 5Private fields and methods (#) were added in ES2022:
class Account {
#balance = 0; // private field
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
const acc = new Account();
acc.deposit(100);
console.log(acc.getBalance()); // 100
console.log(acc.#balance); // error: private property# fields are reachable only inside the class.
Getters and setters let you control how a property is read and changed:
class User {
constructor(name) {
this._name = name;
}
get name() {
return this._name.toUpperCase();
}
set name(value) {
this._name = value.trim();
}
}
const user = new User(' tim ');
console.log(user.name); // TIM
user.name = ' Alex ';
console.log(user.name); // ALEXClass versus constructor function
| Criterion | Constructor function | Class |
|---|---|---|
| Syntax | Old, verbose | Modern, declarative |
new required | Yes | Yes |
| Methods | Through prototype | Inside the class body |
this behaviour | Can be unpredictable | Clear and standard |
extends support | Manual | Built in |
| Private fields | None | Yes (through #) |
A short syntax recap:
| Concept | What it does |
|---|---|
class | Declares a class |
constructor | The method invoked by new |
extends | Inheritance |
super() | A call to the parent constructor |
static | Methods of the class, not of an instance |
#field | A private property |
get / set | Control over property access |
Common mistakes
-
Assuming classes are hoisted the way functions are. A class declaration lands in the temporal dead zone, so touching it above the
classline throws:javascriptconst user = new User(); // ReferenceError class User {} -
Calling a class without
new.User('Alex')throwsTypeError: Class constructor User cannot be invoked without 'new', while an old constructor function in the same call would quietly pollute the global object. -
Touching
thisbefore callingsuper(). In a derived classthisdoes not exist until the parent constructor has run, so you get aReferenceError. -
Expecting class methods in
for...in. They are non-enumerable, unlike properties assigned to a prototype by hand. -
Passing a method as a callback and losing
this.setTimeout(user.sayHi, 0)calls the method unbound; you needuser.sayHi.bind(user), an arrow wrapper, or a class field holding an arrow function. -
Confusing
#balancewith the_balancenaming convention. The underscore is only an agreement between developers, while a#field really is unreachable from outside: referencing it outside the class body is a syntax error.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.