Frontend Master

JavaScript Curriculum

Blueprints for Objects
+50 XP

Blueprints for Objects

medium
~25 min·50 XP

The Nexus backend models users with plain objects — { name, role, xp }. But the codebase has grown: promoting a user, checking permissions, formatting their display name — that logic is scattered across dozens of files. Classes collect data and behaviour together. One User class, one place for all user logic, one blueprint that every part of the system can trust.

Classes are blueprints

A class defines the shape and behaviour of objects. You write the blueprint once, then create as many instances as you need — each with its own data, all sharing the same methods.

js
class User { constructor(name, role) { this.name = name this.role = role this.xp = 0 } greet() { return `Hello, I'm ${this.name}` } } const alex = new User('Alex Chen', 'admin') const jordan = new User('Jordan', 'viewer') alex.greet() // "Hello, I'm Alex Chen" jordan.greet() // "Hello, I'm Jordan" alex.xp // 0 — each instance has its own copy

new User() runs the constructor, allocates a fresh object, and returns it. this inside the class always refers to that specific instance.

Click any part of the class to understand what it does:

class anatomy — click any highlighted part
class User {
xp = 0
#passwordHash = null
 
constructor(name, role) {
this.name = name
this.role = role
}
 
greet() {
return `Hello, I'm ${this.name}`
}
 
get level() {
return Math.floor(this.xp / 1000)
}
 
static create(name) {
return new User(name, 'viewer')
}
}

Click a coloured line to learn about that part of the class.


Class fields — declaring properties at the top

Modern JavaScript lets you declare instance properties at the class body level, not only inside the constructor:

js
class User { // Public field — has a default, overridden by constructor xp = 0 level = 1 // Private field — declared with # — only accessible inside this class #passwordHash = null constructor(name, role, passwordHash) { this.name = name this.role = role this.#passwordHash = passwordHash // OK — we're inside the class } checkPassword(input) { return hash(input) === this.#passwordHash // OK — still inside } } const u = new User('Alex', 'admin', 'abc123') u.#passwordHash // SyntaxError — private!

Private fields with # are truly private — enforced by the JavaScript engine, not just convention. They don't appear in Object.keys(), JSON.stringify(), or the console.


Getters and setters

Getters let you expose computed values as if they were plain properties:

js
class User { xp = 0 get level() { return Math.floor(this.xp / 1000) } set displayName(value) { this.name = value.trim() } } const u = new User('Alex', 'admin') u.xp = 2500 u.level // 2 — no parentheses, looks like a property u.displayName = ' Alex Chen ' // triggers the setter, trims it

Use getters for derived values that should stay in sync with underlying data. Use setters to add validation when a property is assigned.


Static methods and properties

Static members belong to the class itself, not to instances:

js
class User { static DEFAULT_ROLE = 'viewer' static #count = 0 constructor(name) { this.name = name User.#count++ } static create(name) { return new User(name) // factory method } static getCount() { return User.#count } } User.create('Jordan') // called on the class User.DEFAULT_ROLE // 'viewer' User.getCount() // 1 const u = new User('Alex') u.create // undefined — not on instances

Factory methods (User.create()), utility functions, and constants that belong to the concept but not to any specific instance are the primary uses of static.


Inheritance — extends and super

One class can extend another, inheriting all its properties and methods:

inheritance — step through class extends and super()

base class

class User {
name; xp = 0
constructor(name, role) { ... }
greet() { ... }
promote(xp) { ... }
}

derived class

class AdminUser extends User {
constructor(name, dept) {
super(name, "admin")
this.department = dept
}
viewLogs() { ... }
}
step 1/6Define base class User

User is the base class. It defines name, role, xp, greet(), and promote().

js
class User { constructor(name, role) { this.name = name this.role = role this.xp = 0 } greet() { return `Hello, I'm ${this.name}` } promote(xp) { this.xp += xp } } class AdminUser extends User { constructor(name, department) { super(name, 'admin') // MUST call super() before using this this.department = department } viewLogs() { return `${this.name} viewed system logs` } banUser(target){ return `${this.name} banned ${target}` } } const admin = new AdminUser('Jordan', 'Security') admin.greet() // "Hello, I'm Jordan" ← inherited from User admin.viewLogs() // "Jordan viewed system logs" ← own method admin.xp // 0 ← inherited field

Key rules:

  • super(args) must be called in the derived constructor before you access this
  • Derived classes inherit all public methods and fields from the base
  • Private fields (#) are not inherited — they're invisible to subclasses
  • You can override an inherited method by defining it again in the subclass

Method overriding

js
class Animal { speak() { return `${this.name} makes a sound` } } class Dog extends Animal { speak() { // Call the parent version, then add to it: return super.speak() + ' — it barks!' } } const d = new Dog() d.name = 'Rex' d.speak() // "Rex makes a sound — it barks!"

super.method() calls the parent class's version of a method. Use it when you want to extend rather than replace the parent behaviour.


Classes vs plain objects

Classes aren't mandatory. Plain objects and functions can do everything classes do. Use classes when:

  • Multiple instances share methods (classes put methods on the prototype — one copy for all instances)
  • You need inheritance (extends)
  • You want private state (#privateField)
  • The codebase already uses class conventions (React class components, most backend frameworks)

For simple data containers, plain objects are often cleaner. For entities with behaviour that many parts of the codebase interact with, classes give you a clear, well-understood structure.

Challenge

Define a class called Animal with a constructor that takes name and sound. Add a method speak() that returns name + ' says ' + sound + '!'. Add a static method describe() that returns 'Animals communicate through sound.'. Then define a class Dog that extends Animal. Dog's constructor takes only name and calls super with name and 'woof'. Add a method fetch(item) that returns name + ' fetches the ' + item + '!'. Create a Dog instance called rex with name 'Rex' and test all methods.

classconstructormethodsstaticextendssuperinheritanceprivate-fieldsgetters
Blueprints for Objects | Nexus Learn