Classes & Instances

Advanced · 18 min read · ▶ live playground · ✦ checkpoint

Two players can keep separate scores while sharing one method body. That is what a class gives you: a blueprint for making related objects. An instance is one object made from that class, with its own data and a link back to the class's shared methods.

Write a class

A constructor is the setup method that runs when new creates an instance. A public field is an own property a class adds to each instance, readable and writable with normal dot syntax. A method is a function on the class's prototype that instances can call.

Use PascalCase for class names: PracticeCard, not practiceCard. The blueprint name should read like the kind of object it creates.

class PracticeCard {
  mood = "ready";
 
  constructor(playerName, score) {
    this.playerName = playerName;
    this.score = score;
  }
 
  addPoints(points) {
    this.score = this.score + points;
    return this.score;
  }
 
  label() {
    return `${this.playerName}: ${this.score} (${this.mood})`;
  }
}
 
const mina = new PracticeCard("Mina", 18);
const rae = new PracticeCard("Rae", 12);
 
mina.addPoints(4);
rae.mood = "focused";
 
console.log(mina.label());
console.log(rae.label());
console.log(Object.hasOwn(mina, "mood"));
console.log(Object.hasOwn(PracticeCard.prototype, "label"));

Mina: 22 (ready), Rae: 12 (focused), true, then true.

mood, playerName, and score are own properties on each instance. label and addPoints are shared methods. You get one blueprint, many object cards.

Public means ordinary code can touch the field. rae.mood = "focused" is legal because mood is public. The private version, with fields that outside code cannot read directly, comes in lesson 11.3.

Methods still live on the prototype

Class syntax looks new, but the prototype truth from 11.1 stays visible. The instance is an object in the heap. Its prototype is ClassName.prototype, the backup card where class methods live.

class PlayerBadge {
  constructor(playerName, score) {
    this.playerName = playerName;
    this.score = score;
  }
 
  label() {
    return `${this.playerName}: ${this.score}`;
  }
}
 
const badge = new PlayerBadge("Ira", 16);
 
console.log(Object.getPrototypeOf(badge) === PlayerBadge.prototype);
console.log(Object.hasOwn(badge, "label"));
console.log(Object.hasOwn(PlayerBadge.prototype, "label"));
console.log(badge.label === PlayerBadge.prototype.label);

true, false, true, then true.

badge does not own label. Property lookup walks to PlayerBadge.prototype, finds the method, and then the call site badge.label() still makes this be badge.

That is the reason methods are memory-friendly. If you create 1,000 badges, each one gets its own playerName and score, but they can all use the same label function.

What new actually does

The new operator creates an instance from a class. Older constructor functions use it too, but classes are the main spelling now. Think of new as a short assembly line:

  1. Create a fresh object in the heap.
  2. Link that object to ClassName.prototype.
  3. Add public fields to the fresh object.
  4. Run the constructor with this set to that fresh object.
  5. Return the fresh object automatically.

For the plain classes you are writing here, public fields are ready before the constructor body runs:

class RoundTicket {
  status = "open";
 
  constructor(playerName, seat) {
    console.log(`status during setup: ${this.status}`);
    console.log(`this is a RoundTicket: ${this instanceof RoundTicket}`);
    this.playerName = playerName;
    this.seat = seat;
  }
 
  summary() {
    return `${this.playerName} sits at ${this.seat} (${this.status})`;
  }
}
 
const ticket = new RoundTicket("Bo", "A4");
 
console.log(ticket.summary());
console.log(Object.getPrototypeOf(ticket) === RoundTicket.prototype);

status during setup: open, this is a RoundTicket: true, Bo sits at A4 (open), then true.

Inside the constructor, this is not mysterious. It is the fresh object currently moving through that assembly line. Constructor assignments like this.playerName = playerName write own properties onto that object.

Try it — build two score cards

Run this, then change only nico.bonus at the prompt. sam keeps a separate field, but both objects still share the same methods through ScoreCard.prototype.

javascript — live consolereal engine
⌘/Ctrl + Enter to run

Check the family before using it

The instanceof operator checks whether a constructor's .prototype appears somewhere in an object's prototype chain. A brand check is a test that asks whether a value belongs to the object family your code expects before you use family-specific behavior.

class ScoreCard {
  constructor(playerName, score) {
    this.playerName = playerName;
    this.score = score;
  }
 
  label() {
    return `${this.playerName}: ${this.score}`;
  }
}
 
const realCard = new ScoreCard("Nia", 20);
const lookAlike = {
  playerName: "Nia",
  score: 20,
  label() {
    return `${this.playerName}: ${this.score}`;
  },
};
const rowData = ["Nia", 20];
 
console.log(realCard instanceof ScoreCard);
console.log(lookAlike instanceof ScoreCard);
console.log(rowData instanceof ScoreCard);
console.log(Array.isArray(rowData));

true, false, false, then true.

lookAlike has the same property names and even a matching method, but its prototype chain does not include ScoreCard.prototype. Shape is not the brand. instanceof checks the prototype chain.

Built-ins often have their own brand helpers. Array.isArray(rowData) is the right check for arrays because it asks the array question directly. Private fields give classes a stronger brand-check tool in lesson 11.3; for now, keep instanceof tied to prototypes, not matching property names.

You can now read a class without losing sight of the objects underneath it: instances own their data, methods live on the prototype, and new wires the two together. Next, you will add true privacy so an instance can protect its internal state instead of trusting every caller.

Checkpoint

Answer all three to mark this lesson complete

+50 XP on completion