JavaScript Classes
This lesson explains JavaScript Classes with clear examples, use cases, and best practices so you can use modern JavaScript confidently in real projects.
ES6 Classes Overview
ES6 classes provide a cleaner syntax for creating objects, constructors,
methods, inheritance, and reusable object-oriented code in JavaScript.
Classes are built on top of JavaScript prototypes, but they make object
creation easier to read and organize.
Classes are commonly used for models, services, utilities, custom
components, API clients, state managers, design system components,
reusable business logic, and object-oriented application architecture.
| Feature | Description | Example Syntax |
| Class | Blueprint for creating objects. | class User { ... } |
| Constructor | Runs when a new object is created. | constructor(name) { ... } |
| Method | Function defined inside a class. | login() { ... } |
| Inheritance | Create a class from another class. | class Admin extends User |
| super | Calls parent constructor or methods. | super(name) |
| Static Method | Method called on class, not instance. | static create() { ... } |
Basic Class Example
class User {
constructor(name, email) {
this.name =
name;
this.email =
email;
}
showProfile() {
return this.name + " - " + this.email;
}
}
const user =
new User(
"Alice",
"alice@example.com"
);
console.log(
user.showProfile()
);
The class keyword defines a class. The
constructor method initializes values when the object is
created with new.
Constructor Method
class Product {
constructor(name, price) {
this.name =
name;
this.price =
price;
}
}
const product =
new Product(
"Laptop",
1200
);
console.log(product.name);
console.log(product.price);
A constructor is used to set initial properties for each object instance.
A class can have only one constructor method.
Instance Methods
class Cart {
constructor() {
this.items =
[];
}
addItem(item) {
this.items.push(item);
}
getTotalItems() {
return this.items.length;
}
}
const cart =
new Cart();
cart.addItem("Keyboard");
cart.addItem("Mouse");
console.log(
cart.getTotalItems()
);
Instance methods are called on objects created from the class. They can
access instance properties using this.
this Keyword in Classes
class Counter {
constructor() {
this.count =
0;
}
increment() {
this.count += 1;
return this.count;
}
}
const counter =
new Counter();
console.log(
counter.increment()
);
Inside class methods, this refers to the current object
instance.
Class Expression
const User =
class {
constructor(name) {
this.name =
name;
}
greet() {
return "Hello " + this.name;
}
};
const user =
new User("John");
console.log(
user.greet()
);
Classes can also be stored in variables as class expressions, although
class declarations are more common.
Getters and Setters
class Employee {
constructor(firstName, lastName) {
this.firstName =
firstName;
this.lastName =
lastName;
}
get fullName() {
return this.firstName + " " + this.lastName;
}
set fullName(value) {
const parts =
value.split(" ");
this.firstName =
parts[0];
this.lastName =
parts[1];
}
}
const employee =
new Employee(
"Alice",
"Johnson"
);
console.log(employee.fullName);
employee.fullName =
"Bob Smith";
console.log(employee.fullName);
Getters read computed values like properties. Setters update values using
property assignment syntax.
Static Methods
class MathHelper {
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
}
console.log(
MathHelper.add(10, 20)
);
console.log(
MathHelper.multiply(5, 4)
);
Static methods belong to the class itself, not to object instances. They
are useful for utility methods, factories, validation helpers, and parsing
logic.
Static Properties
class AppConfig {
static appName =
"Admin Dashboard";
static version =
"1.0.0";
static getInfo() {
return this.appName + " " + this.version;
}
}
console.log(
AppConfig.getInfo()
);
Static properties store values on the class itself. They are useful for
constants, configuration, shared labels, and metadata.
Class Inheritance with extends
class User {
constructor(name) {
this.name =
name;
}
login() {
return this.name + " logged in.";
}
}
class Admin extends User {
deleteUser(userName) {
return userName + " deleted.";
}
}
const admin =
new Admin("Sarah");
console.log(
admin.login()
);
console.log(
admin.deleteUser("John")
);
The extends keyword creates a child class that inherits
properties and methods from a parent class.
Using super in Constructor
class User {
constructor(name, email) {
this.name =
name;
this.email =
email;
}
}
class Admin extends User {
constructor(name, email, role) {
super(name, email);
this.role =
role;
}
}
const admin =
new Admin(
"Alice",
"alice@example.com",
"Manager"
);
console.log(admin.name);
console.log(admin.role);
In a child class constructor, super() must be called before
using this.
Calling Parent Methods with super
class Notification {
send(message) {
return "Sending: " + message;
}
}
class EmailNotification extends Notification {
send(message) {
const baseMessage =
super.send(message);
return baseMessage + " by email.";
}
}
const email =
new EmailNotification();
console.log(
email.send("Welcome")
);
super.methodName() calls a method from the parent class.
Method Overriding
class Animal {
speak() {
return "Animal sound";
}
}
class Dog extends Animal {
speak() {
return "Dog barks";
}
}
const dog =
new Dog();
console.log(
dog.speak()
);
A child class can define a method with the same name as the parent method.
This is called method overriding.
Private Class Fields
class BankAccount {
#balance =
0;
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
}
getBalance() {
return this.#balance;
}
}
const account =
new BankAccount();
account.deposit(100);
console.log(
account.getBalance()
);
Private fields use the # prefix and cannot be accessed
directly from outside the class.
Private Methods
class Validator {
#isEmail(value) {
return value.includes("@");
}
validateEmail(value) {
return this.#isEmail(value);
}
}
const validator =
new Validator();
console.log(
validator.validateEmail("test@example.com")
);
Private methods are useful for internal class logic that should not be
exposed to consumers.
Public Class Fields
class TodoStore {
todos =
[];
addTodo(text) {
this.todos.push(
{
text,
completed: false
}
);
}
}
const store =
new TodoStore();
store.addTodo("Learn ES6 classes");
console.log(store.todos);
Public fields can be declared directly inside a class without writing them
inside the constructor.
Arrow Function Class Field
class ButtonController {
count =
0;
handleClick =
() => {
this.count += 1;
console.log(this.count);
};
}
const controller =
new ButtonController();
document
.querySelector("#button")
.addEventListener(
"click",
controller.handleClick
);
Arrow function fields keep this bound to the class instance.
This is useful for event handlers in UI code.
Real-World Example: API Client Class
class ApiClient {
constructor(baseUrl) {
this.baseUrl =
baseUrl;
}
async get(path) {
const response =
await fetch(this.baseUrl + path);
if (!response.ok) {
throw new Error(
"Request failed"
);
}
return response.json();
}
async post(path, data) {
const response =
await fetch(
this.baseUrl + path,
{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
}
);
return response.json();
}
}
const api =
new ApiClient("/api");
api.get("/users")
.then((users) => {
console.log(users);
});
Classes are useful for grouping related API methods and shared
configuration.
Real-World Example: Model Class
class Product {
constructor(data) {
this.id =
data.id;
this.name =
data.name;
this.price =
data.price;
}
getFormattedPrice() {
return "$" + this.price.toFixed(2);
}
isExpensive() {
return this.price > 1000;
}
}
const product =
new Product(
{
id: 1,
name: "Laptop",
price: 1299
}
);
console.log(
product.getFormattedPrice()
);
Model classes can group data and behavior together in a readable way.
Real-World Example: UI Component Class
class Modal {
constructor(selector) {
this.element =
document.querySelector(selector);
}
open() {
this.element.hidden =
false;
}
close() {
this.element.hidden =
true;
}
}
const modal =
new Modal("#loginModal");
document
.querySelector("#openModal")
.addEventListener("click", () => {
modal.open();
});
UI classes can manage DOM elements, state, and behavior for reusable
components.
Classes vs Constructor Functions
// Constructor function
function User(name) {
this.name =
name;
}
User.prototype.greet =
function() {
return "Hello " + this.name;
}
// ES6 class
class ModernUser {
constructor(name) {
this.name =
name;
}
greet() {
return "Hello " + this.name;
}
}
Classes are mostly cleaner syntax over prototype-based object creation.
Classes Use Prototypes
class User {
greet() {
return "Hello";
}
}
const user =
new User();
console.log(
User.prototype.greet === user.__proto__.greet
);
Class methods are stored on the prototype, not copied into every instance.
This helps memory usage.
instanceof with Classes
class User {}
const user =
new User();
console.log(
user instanceof User
);
// true
The instanceof operator checks whether an object was created
from a class or its prototype chain.
Class vs Object Literal
| Feature | Class | Object Literal |
| Best For | Creating many similar objects. | Single object or configuration. |
| Constructor | Yes. | No special constructor. |
| Inheritance | Supported with extends. | Manual composition preferred. |
| Methods | Shared through prototype. | Stored directly on object. |
| Common Use | Models, services, components. | Settings, constants, simple data. |
Common ES6 Class Use Cases
- Creating reusable model objects.
- Building API client services.
- Managing UI components.
- Creating validation utilities.
- Building data stores.
- Writing custom elements and Web Components.
- Organizing business rules.
- Creating reusable framework-independent modules.
Common ES6 Class Mistakes
- Forgetting to use
new when creating an instance. - Using
this before super() in a child constructor. - Using classes for simple data that could be plain objects.
- Creating deep inheritance chains that are hard to maintain.
- Forgetting that class methods are not automatically bound.
- Confusing static methods with instance methods.
- Expecting private fields to be accessible outside the class.
- Putting too much unrelated logic into one large class.
ES6 Class Best Practices
- Use classes when you need many similar objects with shared behavior.
- Use object literals for simple configuration or one-off objects.
- Keep class responsibilities focused.
- Prefer composition over deep inheritance.
- Use private fields for internal state when needed.
- Use static methods for utility behavior related to the class.
- Use clear constructor parameters or object options.
- Write small methods with clear names.
Key Takeaways
- ES6 classes provide cleaner syntax for prototype-based objects.
- The
constructor method initializes object instances. - Instance methods are shared through the prototype.
extends creates inheritance between classes. super() calls the parent constructor or method. - Static methods belong to the class, not the instance.
- Private fields use the
# prefix. - Use classes only when they improve organization and reuse.
Pro Tip
In interviews, remember this simple explanation: ES6 classes are cleaner
syntax over JavaScript's prototype system. Use them for reusable objects,
services, models, and components, but avoid deep inheritance when
composition would be simpler.