Difficulty: Intermediate
Explain ES6 classes in JavaScript. How do inheritance, static methods, and private fields work?
ES6 classes are syntactic sugar over JavaScript's prototype-based inheritance. They provide a clearer, more familiar syntax for creating constructor functions and setting up prototype chains.
A class body can contain a constructor, instance methods, static methods, getters/setters, and private fields (prefixed with #). The 'extends' keyword creates a subclass, and 'super' calls the parent class constructor or methods.
Static methods belong to the class itself, not instances. Private fields (ES2022) with the # prefix are truly private - they cannot be accessed outside the class, unlike the older _convention which was just a naming hint.
Despite the class syntax, understanding that classes are functions and inheritance is prototypal remains important for interviews.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
static create(name) {
return new this(name);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // must call super before using 'this'
this.breed = breed;
}
speak() {
return `${this.name} barks`;
}
info() {
return `${super.speak()} (actually: ${this.speak()})`;
}
}
const dog = Dog.create('Rex');
console.log(dog.speak());
const buddy = new Dog('Buddy', 'Labrador');
console.log(buddy.info());
console.log(buddy instanceof Dog); // true
console.log(buddy instanceof Animal); // true
extends sets up the prototype chain. super() in the constructor calls the parent constructor. super.method() calls the parent's version of an overridden method.
class BankAccount {
#balance; // private field
#owner;
constructor(owner, initialBalance) {
this.#owner = owner;
this.#balance = initialBalance;
}
get balance() {
return `${this.#balance.toFixed(2)}`;
}
set balance(value) {
throw new Error('Use deposit() or withdraw()');
}
deposit(amount) {
if (amount <= 0) throw new Error('Amount must be positive');
this.#balance += amount;
return this;
}
withdraw(amount) {
if (amount > this.#balance) throw new Error('Insufficient funds');
this.#balance -= amount;
return this;
}
toString() {
return `${this.#owner}'s account: ${this.balance}`;
}
}
const acct = new BankAccount('Alice', 1000);
acct.deposit(500).withdraw(200); // chaining
console.log(acct.toString());
console.log(acct.balance);
// console.log(acct.#balance); // SyntaxError: private field
Private fields with # are truly private - accessing them outside the class is a SyntaxError. Getters/setters let you control property access with method logic.
class MathUtils {
static PI = 3.14159;
static add(a, b) { return a + b; }
static multiply(a, b) { return a * b; }
static #formatResult(result) {
return `Result: ${result}`;
}
static calculate(operation, a, b) {
const ops = { add: this.add, multiply: this.multiply };
const result = ops[operation]?.(a, b);
return result !== undefined
? this.#formatResult(result)
: 'Unknown operation';
}
}
console.log(MathUtils.PI);
console.log(MathUtils.add(2, 3));
console.log(MathUtils.calculate('multiply', 4, 5));
// Static methods are NOT on instances
const m = new MathUtils();
console.log(typeof m.add); // undefined
Static methods and properties belong to the class, not instances. They are useful for utility functions and factory methods. Private static methods use #.
class, extends, super, static, Private Fields, Getters/Setters