JavaScript Real World Vademecum
Part II: OOP & Classes
Modern JavaScript relies heavily on Objects and Classes to structure code. In this section, we move from writing simple scripts to engineering scalable software using the Object-Oriented paradigm.
Classes (Object-Oriented Programming)
Classes are your entry point into Object-Oriented Programming (OOP). If so far you’ve built huts with functions and scattered variables (procedural programming), classes are like getting the blueprint to build a skyscraper. They allow you to create reusable, organized, and powerful "types" of things (like Player, Enemy, Platform), each with its own data (properties) and its own abilities (methods).
13. Basic Concept (The Mold)
A class is a blueprint, a recipe, or a cookie cutter.
It is not the cookie. It’s the drawing that tells you how to make a cookie (which will have a shape, a type of chocolate, etc.).
- The Class (e.g.
class Player) is the mold. - The Object (or Instance) (e.g.
const mario = new Player()) is the cookie you created using the mold.
You can use a single mold (Class) to create infinite cookies (Instances), and each cookie is a separate and independent entity.
// 1. DEFINITION OF THE MOLD (The Class)
class Player {
// Here we will define what a player looks like
}
// 2. CREATION OF THE "COOKIES" (The Instances)
const mario = new Player();
const luigi = new Player();
// mario and luigi are two different objects,
// but they were both created from the same Player "mold".
14. Syntactic Sugar (vs Constructor Functions)
This is the most important "secret" about classes in JavaScript: classes are an illusion.
JavaScript, at its core, is a language based on prototypes, not classes. Before ES6, to create "molds" people used something called a "Constructor Function". It was clunky but powerful:
// THE OLD WAY (pre-ES6)
function OldPerson(name) {
this.name = name;
}
OldPerson.prototype.greet = function() {
console.log("Hi, I'm " + this.name);
}
const oldMario = new OldPerson("Mario");
oldMario.greet();
This prototype confused everyone, especially those coming from languages like Python, Java, or C# that used the word class.
So, ES6 introduced the class syntax. But it’s not a new system! It’s just "syntactic sugar". It’s like putting a sporty, modern body on top of the old prototype engine.
// THE NEW WAY (Modern, "sugar")
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hi, I'm ${this.name}`);
}
}
const mario = new Person("Mario");
mario.greet();
The two examples above do exactly the same identical thing. The word class is just a cleaner, more readable "disguise" for creating a constructor function and assigning methods to its prototype.
15. constructor() and Parameters
The constructor() is a special and unique method. It’s the "assembly department" of your factory.
- It is called automatically and only once at the exact moment you use the
newkeyword. - Its job is to set the initial state of the instance (the cookie). This is where you say "this cookie will have chocolate chips" and "this other one will have jam".
Parameters are the "custom ingredients" you pass to the factory to create a specific instance.
class Platform {
// The constructor accepts 'x' and 'y' as ingredients
constructor(x, y) {
console.log("I'm building a platform...");
// 'this' refers to the object we are creating
this.position = { x: x, y: y };
this.width = 100;
this.height = 20;
}
}
// We pass the ingredients (the parameters) when we use 'new'
const platform1 = new Platform(50, 300);
const platform2 = new Platform(250, 400);
// platform1 will have { position: {x: 50, y: 300}, ... }
// platform2 will have { position: {x: 250, y: 400}, ... }
If you don’t need to set anything, you can omit the constructor (JavaScript will use an empty one by default).
16. new (The 4 Steps)
What does the new keyword really do? It’s a magical process that happens in four automatic steps:
- Creates an Empty Object: JavaScript creates a new empty object:
{}. - Binds the Prototype: It "binds" this empty object to the class’s shared "backpack" (the
prototype). (Now it knows where to find thegreet()method). - Runs the Constructor: It runs the
constructorfunction, and sets thethiskeyword to point to the empty object created in step 1. The constructor "fills" the object with properties (this.name = ...). - Returns
this: It automatically returns the object (thethis), now "full" and ready to use.
You don’t do return this in the constructor — new does it for you.
17. this (Instance context)
This is the most important and most confusing concept in OOP.
this is a dynamic keyword. Think of this as the word "I" or "myself".
- If I say "my shirt is blue", "my" refers to me.
- If you say "my shirt is red", "my" refers to you.
Inside a class, this means "this specific instance", "this cookie I’m creating RIGHT NOW" or "this cookie you’re calling the method on".
class Counter {
constructor() {
this.value = 0;
}
increment() {
// 'this' here means "the specific counter
// you called .increment() on"
this.value++;
console.log(this.value);
}
}
const c1 = new Counter();
const c2 = new Counter();
c1.increment(); // Output: 1 (this === c1)
c1.increment(); // Output: 2 (this === c1)
c2.increment(); // Output: 1 (this === c2)
this is the bridge that connects the generic mold (the class) to the concrete object (the instance).
18. Class Properties (Modern Syntax)
This is a modern shortcut (called Class Fields) to make the constructor cleaner. Instead of defining all the "base" properties inside the constructor...
// Classic way
class OldPlayer {
constructor(name) {
this.name = name;
this.lives = 3;
this.score = 0;
}
}
...you can declare them directly in the class body. Think of these as the mold’s "factory settings".
// Modern way (cleaner)
class Player {
// Factory settings
lives = 3;
score = 0;
// The constructor sets only the customized things
constructor(name) {
this.name = name;
}
}
const player = new Player("Sonic");
// player will have { lives: 3, score: 0, name: "Sonic" }
It works exactly the same way — it’s just tidier.
19. Methods (in the prototype)
Where do functions (the "methods") like greet() or increment() end up?
Common mistake: thinking that every instance (every cookie) gets a copy of the function. If you had 1000 players, you’d have 1000 copies of the greet() method. That would waste tons of memory!
Reality: There is only one copy of greet(). It lives in a special shared place called prototype (the class’s "shared backpack").
- When you define a method (
greet() { ... }) inside aclass, JavaScript automatically attaches it toPerson.prototype. - When you call
mario.greet(), JavaScript checks: "Does Mario have agreetmethod on itself?" - "No."
- "Ok, then I’ll check in the 'backpack' (the
prototype) it was created from." - "Ah, there it is! I’ll run it and set
thisso it’smario."
This mechanism (the prototype chain) is incredibly efficient. You have one single instruction manual (prototype) for thousands of objects (instances).
20. Pattern: claim() Method (Object Deactivation)
This is a practical example that ties everything together. Instead of deleting an object from an array (which can be complicated), it’s often easier to "deactivate" it.
It’s a method (a function in the prototype "backpack") that changes the properties (this.width, this.position) of the specific instance (this).
Analogy: It’s like a "self-destruct" button that every object inherits. When checkpoint1.claim() is called, only that checkpoint deactivates, by modifying itself.
class Checkpoint {
constructor(x, y) {
this.position = { x, y };
this.width = 50;
this.height = 70;
this.claimed = false; // Boolean flag
}
// Method to "deactivate" this specific checkpoint
claim() {
console.log("Checkpoint claimed!");
// Modify the properties of *this* instance
this.width = 0; // Becomes invisible
this.height = 0;
this.position.y = Infinity; // Disappears from the game world
this.claimed = true; // Mark as "used"
}
}
// In your game:
const checkpoint1 = new Checkpoint(100, 200);
// ...when the player touches it...
checkpoint1.claim();
// checkpoint1 is now { width: 0, height: 0, position: {y: Infinity}, claimed: true }
// checkpoint2 (another instance) is still intact.
This is called Encapsulation: the logic for "how to deactivate a checkpoint" is contained inside the Checkpoint class itself, instead of being scattered around the code.