docs/concepts/factories-classes.mdx
How do you create hundreds of similar objects without copy-pasting? How do game developers spawn thousands of enemies? How does JavaScript let you build blueprints for objects?
// Factory function — returns a new object each time
function createPlayer(name) {
return {
name,
health: 100,
attack() {
return `${this.name} attacks!`
}
}
}
// Class — a blueprint for creating objects
class Enemy {
constructor(name) {
this.name = name
this.health = 100
}
attack() {
return `${this.name} attacks!`
}
}
// Both create objects the same way
const player = createPlayer("Alice") // Factory
const enemy = new Enemy("Goblin") // Class
console.log(player.attack()) // "Alice attacks!"
console.log(enemy.attack()) // "Goblin attacks!"
Factories and Classes are two patterns for creating objects efficiently. A factory function is a regular function that returns a new object. A class is a blueprint that uses the class keyword and the new operator. Both achieve the same goal, but they work differently and have different strengths. According to the 2023 State of JS survey, class syntax is now widely adopted, with the majority of JavaScript developers using classes regularly in their projects.
Let's say you're building an RPG game. You need player characters:
// Creating players manually — tedious and error-prone
const player1 = {
name: "Alice",
health: 100,
level: 1,
attack() {
return `${this.name} attacks for ${10 + this.level * 2} damage!`;
},
takeDamage(amount) {
this.health -= amount;
if (this.health <= 0) {
return `${this.name} has been defeated!`;
}
return `${this.name} has ${this.health} health remaining.`;
}
};
const player2 = {
name: "Bob",
health: 100,
level: 1,
attack() {
return `${this.name} attacks for ${10 + this.level * 2} damage!`;
},
takeDamage(amount) {
this.health -= amount;
if (this.health <= 0) {
return `${this.name} has been defeated!`;
}
return `${this.name} has ${this.health} health remaining.`;
}
};
// ... 50 more players with the same code copied ...
| Problem | Why It's Bad |
|---|---|
| Repetition | Same code copied over and over |
| Error-prone | Easy to make typos or forget properties |
| Hard to maintain | Change one thing? Change it everywhere |
| No consistency | Nothing enforces that all players have the same structure |
| Memory waste | Each object has its own copy of the methods |
We need a way to:
Think about how real-world manufacturing works:
JavaScript gives us the same options:
┌─────────────────────────────────────────────────────────────────────────┐
│ THREE WAYS TO CREATE OBJECTS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ MANUAL CREATION Like hand-carving each chess piece │
│ ─────────────── Tedious, error-prone, inconsistent │
│ const obj = { ... } │
│ │
│ ───────────────────────────────────────────────────────────────────── │
│ │
│ FACTORY FUNCTION Like an assembly line │
│ ──────────────── Put in specs → Get product │
│ Flexible, no special keywords │
│ createPlayer("Alice") │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Player │ ← New object returned │
│ │ {name...} │ │
│ └─────────────┘ │
│ │
│ ───────────────────────────────────────────────────────────────────── │
│ │
│ CLASS / CONSTRUCTOR Like a blueprint or mold │
│ ─────────────────── Define template → Stamp out copies │
│ Uses `new`, supports `instanceof` │
│ new Player("Alice") │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Player │ ← Instance created from blueprint │
│ │ {name...} │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Both factories and classes solve the same problem. They just do it differently. Let's explore each approach.
A factory function is a regular JavaScript function that creates and returns a new object each time it's called. Unlike constructors or classes, factory functions don't require the new keyword. They can use this in returned methods (like simple objects do), or use closures to avoid this entirely, giving you flexibility that classes don't offer. As Douglas Crockford documented in JavaScript: The Good Parts, factory functions leverage JavaScript's prototypal nature more directly than class-based patterns.
Think of it like an assembly line. You put in the specifications, and it produces the product:
// A simple factory function
function createPlayer(name) {
return {
name: name,
health: 100,
level: 1,
attack() {
return `${this.name} attacks for ${10 + this.level * 2} damage!`;
},
takeDamage(amount) {
this.health -= amount;
if (this.health <= 0) {
return `${this.name} has been defeated!`;
}
return `${this.name} has ${this.health} health remaining.`;
}
};
}
// Creating players is now easy!
const alice = createPlayer("Alice");
const bob = createPlayer("Bob");
const charlie = createPlayer("Charlie");
console.log(alice.attack()); // "Alice attacks for 12 damage!"
console.log(bob.takeDamage(30)); // "Bob has 70 health remaining."
function createEnemy(name, health, attackPower) {
return {
name, // Shorthand: same as name: name
health,
attackPower,
isAlive: true,
attack(target) {
return `${this.name} attacks ${target.name} for ${this.attackPower} damage!`;
},
takeDamage(amount) {
this.health -= amount;
if (this.health <= 0) {
this.health = 0;
this.isAlive = false;
return `${this.name} has been defeated!`;
}
return `${this.name} has ${this.health} health remaining.`;
}
};
}
// Create different types of enemies
const goblin = createEnemy("Goblin", 50, 10);
const dragon = createEnemy("Dragon", 500, 50);
const boss = createEnemy("Dark Lord", 1000, 100);
console.log(goblin.attack(dragon)); // "Goblin attacks Dragon for 10 damage!"
console.log(dragon.takeDamage(100)); // "Dragon has 400 health remaining."
For many options, use a configuration object:
function createCharacter(config) {
// Default values
const defaults = {
name: "Unknown",
health: 100,
maxHealth: 100,
level: 1,
experience: 0,
attackPower: 10,
defense: 5
};
// Merge defaults with provided config
const settings = { ...defaults, ...config };
return {
...settings,
attack(target) {
const damage = Math.max(0, this.attackPower - target.defense);
return `${this.name} deals ${damage} damage to ${target.name}!`;
},
heal(amount) {
this.health = Math.min(this.maxHealth, this.health + amount);
return `${this.name} healed to ${this.health} health.`;
},
gainExperience(amount) {
this.experience += amount;
if (this.experience >= this.level * 100) {
this.level++;
this.experience = 0;
this.attackPower += 5;
return `${this.name} leveled up to ${this.level}!`;
}
return `${this.name} gained ${amount} XP.`;
}
};
}
// Create characters with different configurations
const warrior = createCharacter({
name: "Warrior",
health: 150,
maxHealth: 150,
attackPower: 20,
defense: 10
});
const mage = createCharacter({
name: "Mage",
health: 80,
maxHealth: 80,
attackPower: 30,
defense: 3
});
// Only override what you need
const villager = createCharacter({ name: "Villager" });
A powerful feature of factory functions is creating truly private variables using closures:
function createBankAccount(ownerName, initialBalance = 0) {
// Private variables — NOT accessible from outside
let balance = initialBalance;
const transactionHistory = [];
// Private function
function recordTransaction(type, amount) {
transactionHistory.push({
type,
amount,
balance,
date: new Date().toISOString()
});
}
// Initialize
recordTransaction("opening", initialBalance);
// Return public interface
return {
owner: ownerName,
deposit(amount) {
if (amount <= 0) {
throw new Error("Deposit amount must be positive");
}
balance += amount;
recordTransaction("deposit", amount);
return `Deposited $${amount}. New balance: $${balance}`;
},
withdraw(amount) {
if (amount <= 0) {
throw new Error("Withdrawal amount must be positive");
}
if (amount > balance) {
throw new Error("Insufficient funds");
}
balance -= amount;
recordTransaction("withdrawal", amount);
return `Withdrew $${amount}. New balance: $${balance}`;
},
getBalance() {
return balance;
},
getStatement() {
return transactionHistory.map(t =>
`${t.date}: ${t.type} $${t.amount} (Balance: $${t.balance})`
).join('\n');
}
};
}
const account = createBankAccount("Alice", 1000);
console.log(account.deposit(500)); // "Deposited $500. New balance: $1500"
console.log(account.withdraw(200)); // "Withdrew $200. New balance: $1300"
console.log(account.getBalance()); // 1300
// Trying to access private variables — FAILS!
console.log(account.balance); // undefined
console.log(account.transactionHistory); // undefined
// Can't cheat!
account.balance = 1000000; // Does nothing useful
console.log(account.getBalance()); // Still 1300
Factories can return different object types based on input:
function createWeapon(type) {
const weapons = {
sword: {
name: "Iron Sword",
damage: 25,
speed: "medium",
attack() {
return `Slash with ${this.name} for ${this.damage} damage!`;
}
},
bow: {
name: "Longbow",
damage: 20,
speed: "fast",
range: 100,
attack() {
return `Fire an arrow for ${this.damage} damage from ${this.range}m away!`;
}
},
staff: {
name: "Magic Staff",
damage: 35,
speed: "slow",
manaCost: 10,
attack() {
return `Cast a spell for ${this.damage} damage! (Costs ${this.manaCost} mana)`;
}
}
};
if (!weapons[type]) {
throw new Error(`Unknown weapon type: ${type}`);
}
return { ...weapons[type] }; // Return a copy
}
const sword = createWeapon("sword");
const bow = createWeapon("bow");
const staff = createWeapon("staff");
console.log(sword.attack()); // "Slash with Iron Sword for 25 damage!"
console.log(bow.attack()); // "Fire an arrow for 20 damage from 100m away!"
console.log(staff.attack()); // "Cast a spell for 35 damage! (Costs 10 mana)"
A constructor function is a regular JavaScript function designed to be called with the new keyword. When invoked with new, it creates a new object, binds this to that object, and returns it automatically. Constructor names conventionally start with a capital letter to distinguish them from regular functions. This was the standard way to create objects before ES6 classes.
// Convention: Constructor names start with a capital letter
function Player(name) {
// 'this' refers to the new object being created
this.name = name;
this.health = 100;
this.level = 1;
this.attack = function() {
return `${this.name} attacks for ${10 + this.level * 2} damage!`;
};
}
// Create instances with 'new'
const alice = new Player("Alice");
const bob = new Player("Bob");
console.log(alice.name); // "Alice"
console.log(bob.attack()); // "Bob attacks for 12 damage!"
console.log(alice instanceof Player); // true
new Keyword — What It Actually DoesWhen you call new Player("Alice"), JavaScript performs 4 steps:
There's a problem with our constructor: each instance gets its own copy of methods:
function Player(name) {
this.name = name;
this.health = 100;
// BAD: Every player gets their own copy of this function
this.attack = function() {
return `${this.name} attacks!`;
};
}
const p1 = new Player("Alice");
const p2 = new Player("Bob");
// These are different functions!
console.log(p1.attack === p2.attack); // false
// 1000 players = 1000 copies of attack function = wasted memory!
The solution is to put methods on the prototype:
function Player(name) {
this.name = name;
this.health = 100;
// Don't put methods here!
}
// Add methods to the prototype — shared by all instances
Player.prototype.attack = function() {
return `${this.name} attacks!`;
};
Player.prototype.takeDamage = function(amount) {
this.health -= amount;
return `${this.name} has ${this.health} health.`;
};
const p1 = new Player("Alice");
const p2 = new Player("Bob");
// Now they share the same function!
console.log(p1.attack === p2.attack); // true
// 1000 players = 1 copy of attack function = efficient!
┌─────────────────────────────────────────────────────────────────────┐
│ PROTOTYPE CHAIN │
│ │
│ Player.prototype │
│ ┌─────────────────────────┐ │
│ │ attack: function() │ │
│ │ takeDamage: function() │◄──── Shared by all instances │
│ └─────────────────────────┘ │
│ ▲ │
│ │ [[Prototype]] │
│ │ │
│ ┌──────────┴──────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ │
│ │ p1 │ │ p2 │ │
│ │─────────│ │─────────│ │
│ │name: │ │name: │ │
│ │"Alice" │ │"Bob" │ │
│ │health: │ │health: │ │
│ │100 │ │100 │ │
│ └─────────┘ └─────────┘ │
│ │
│ Each instance has its own data, but shares methods via prototype │
└─────────────────────────────────────────────────────────────────────┘
instanceof Operatorinstanceof checks if an object was created by a constructor:
function Player(name) {
this.name = name;
}
function Enemy(name) {
this.name = name;
}
const alice = new Player("Alice");
const goblin = new Enemy("Goblin");
console.log(alice instanceof Player); // true
console.log(alice instanceof Enemy); // false
console.log(goblin instanceof Enemy); // true
console.log(goblin instanceof Player); // false
// Both are instances of Object
console.log(alice instanceof Object); // true
console.log(goblin instanceof Object); // true
newfunction Player(name) {
this.name = name;
this.health = 100;
}
// Oops! Forgot 'new'
const alice = Player("Alice");
console.log(alice); // undefined (function returned nothing)
console.log(name); // "Alice" — LEAKED to global scope!
console.log(health); // 100 — ALSO leaked!
// In strict mode, this would throw an error instead
// 'use strict';
// Player("Alice"); // TypeError: Cannot set property 'name' of undefined
An ES6 class is JavaScript's modern syntax for creating constructor functions and prototypes. Introduced in ECMAScript 2015, classes provide a cleaner, more familiar syntax for object-oriented programming while working exactly the same as constructor functions under the hood. They're often called "syntactic sugar." Classes use the class keyword and require the new operator to create instances.
class Player {
constructor(name) {
this.name = name;
this.health = 100;
this.level = 1;
}
attack() {
return `${this.name} attacks for ${10 + this.level * 2} damage!`;
}
takeDamage(amount) {
this.health -= amount;
if (this.health <= 0) {
return `${this.name} has been defeated!`;
}
return `${this.name} has ${this.health} health remaining.`;
}
}
const alice = new Player("Alice");
console.log(alice.attack()); // "Alice attacks for 12 damage!"
console.log(alice instanceof Player); // true
Classes don't add new functionality. They're just a nicer way to write constructor functions. Under the hood, they work exactly the same:
<Tabs> <Tab title="ES6 Class"> ```javascript class Enemy { constructor(name, health) { this.name = name; this.health = health; } attack() {
return `${this.name} attacks!`;
}
static createBoss(name) {
return new Enemy(name, 1000);
}
}
```
Enemy.prototype.attack = function() {
return `${this.name} attacks!`;
};
Enemy.createBoss = function(name) {
return new Enemy(name, 1000);
};
```
Both create objects with the same structure:
// Both versions produce:
const goblin = new Enemy("Goblin", 100);
console.log(typeof Enemy); // "function" (classes ARE functions!)
console.log(goblin.constructor === Enemy); // true
console.log(goblin.__proto__ === Enemy.prototype); // true
class Character {
// Class field (public property with default value)
level = 1;
experience = 0;
// Constructor — called when you use 'new'
constructor(name, health = 100) {
this.name = name;
this.health = health;
}
// Instance method — available on all instances
attack() {
return `${this.name} attacks!`;
}
// Another instance method
heal(amount) {
this.health += amount;
return `${this.name} healed to ${this.health} HP.`;
}
// Getter — accessed like a property
get isAlive() {
return this.health > 0;
}
// Setter — assigned like a property
set healthPoints(value) {
this.health = Math.max(0, value); // Can't go below 0
}
// Static method — called on the class, not instances
static createHero(name) {
return new Character(name, 150);
}
// Static property
static MAX_LEVEL = 99;
}
// Usage
const hero = Character.createHero("Alice"); // Static method
console.log(hero.attack()); // Instance method
console.log(hero.isAlive); // Getter (no parentheses!)
hero.healthPoints = -50; // Setter
console.log(hero.health); // 0 (setter prevented negative)
console.log(Character.MAX_LEVEL); // 99 (static property)
Static members belong to the class itself, not to instances:
class MathUtils {
// Static properties
static PI = 3.14159;
static E = 2.71828;
// Static methods
static square(x) {
return x * x;
}
static cube(x) {
return x * x * x;
}
static randomBetween(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
// Access via class name
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.square(5)); // 25
// NOT via instances!
const utils = new MathUtils();
console.log(utils.PI); // undefined
console.log(utils.square); // undefined
Common uses for static methods:
User.fromJSON(data))Array.isArray(value))Config.getInstance())Getters and setters let you define computed properties and add validation:
class Temperature {
constructor(celsius) {
this._celsius = celsius; // Convention: underscore = "private"
}
// Getter: accessed like a property
get celsius() {
return this._celsius;
}
// Setter: assigned like a property
set celsius(value) {
if (value < -273.15) {
throw new Error("Temperature below absolute zero!");
}
this._celsius = value;
}
// Computed getter: fahrenheit from celsius
get fahrenheit() {
return this._celsius * 9/5 + 32;
}
// Computed setter: set celsius from fahrenheit
set fahrenheit(value) {
this.celsius = (value - 32) * 5/9; // Uses celsius setter for validation
}
// Read-only getter (no setter)
get kelvin() {
return this._celsius + 273.15;
}
}
const temp = new Temperature(25);
console.log(temp.celsius); // 25
console.log(temp.fahrenheit); // 77
console.log(temp.kelvin); // 298.15
temp.fahrenheit = 100; // Set via fahrenheit
console.log(temp.celsius); // ~37.78 (converted)
// temp.celsius = -300; // Error: Temperature below absolute zero!
// temp.kelvin = 0; // Error: no setter (read-only)
ES2020 introduced private fields with the # prefix. Unlike the _underscore convention, these are truly private:
class BankAccount {
// Private fields — declared with #
#balance = 0;
#pin;
#transactionHistory = [];
constructor(ownerName, initialBalance, pin) {
this.ownerName = ownerName; // Public
this.#balance = initialBalance;
this.#pin = pin;
}
// Private method
#recordTransaction(type, amount) {
this.#transactionHistory.push({
type,
amount,
balance: this.#balance,
date: new Date()
});
}
// Private method for PIN verification
#verifyPin(pin) {
return this.#pin === pin;
}
// Public methods
deposit(amount) {
if (amount <= 0) throw new Error("Invalid amount");
this.#balance += amount;
this.#recordTransaction("deposit", amount);
return this.#balance;
}
withdraw(amount, pin) {
if (!this.#verifyPin(pin)) {
throw new Error("Invalid PIN");
}
if (amount > this.#balance) {
throw new Error("Insufficient funds");
}
this.#balance -= amount;
this.#recordTransaction("withdrawal", amount);
return this.#balance;
}
getBalance(pin) {
if (!this.#verifyPin(pin)) {
throw new Error("Invalid PIN");
}
return this.#balance;
}
}
const account = new BankAccount("Alice", 1000, "1234");
account.deposit(500);
console.log(account.withdraw(200, "1234")); // 1300
console.log(account.getBalance("1234")); // 1300
// Trying to access private fields — ALL FAIL
// account.#balance; // SyntaxError!
// account.#pin; // SyntaxError!
// account.#verifyPin("1234"); // SyntaxError!
console.log(account.balance); // undefined (different property)
Both provide true privacy, but they work differently:
| Feature | Private Fields (#) | Closures (Factory) |
|---|---|---|
| Syntax | this.#field | let variable inside function |
| Access error | SyntaxError | Returns undefined |
| Memory | Efficient (prototype methods) | Each instance has own methods |
instanceof | Works | Doesn't work |
| Inheritance | Private per class | Not inherited |
| Debugger visibility | Visible but inaccessible | Visible in closure scope |
// Private Fields (#)
class Wallet {
#balance = 0;
deposit(amount) { this.#balance += amount; }
getBalance() { return this.#balance; }
}
const w1 = new Wallet();
const w2 = new Wallet();
console.log(w1.deposit === w2.deposit); // true (shared via prototype)
// Closure-based (Factory)
function createWallet() {
let balance = 0;
return {
deposit(amount) { balance += amount; },
getBalance() { return balance; }
};
}
const w3 = createWallet();
const w4 = createWallet();
console.log(w3.deposit === w4.deposit); // false (each has own copy)
When working with factories and classes, there are several common pitfalls that trip up developers. Let's look at the most frequent mistakes and how to avoid them.
┌─────────────────────────────────────────────────────────────────────────┐
│ THE 3 MOST COMMON MISTAKES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. FORGETTING `new` WITH CONSTRUCTORS │
│ Pollutes global scope or crashes in strict mode │
│ │
│ 2. FORGETTING `super()` IN DERIVED CLASSES │
│ Must call super() before using `this` │
│ │
│ 3. CONFUSING `_private` WITH TRULY PRIVATE │
│ Underscore is just a convention, not enforcement │
│ │
└─────────────────────────────────────────────────────────────────────────┘
new with Constructor Functions// ❌ WRONG - Forgot 'new', 'this' becomes global object
function Player(name) {
this.name = name;
this.health = 100;
}
const alice = Player("Alice"); // Missing 'new'!
console.log(alice); // undefined
console.log(globalThis.name); // "Alice" - leaked to global!
console.log(globalThis.health); // 100 - also leaked!
// ✓ CORRECT - Always use 'new' with constructors
const bob = new Player("Bob");
console.log(bob.name); // "Bob"
console.log(bob.health); // 100
class Player {
constructor(name) { this.name = name; }
}
const alice = Player("Alice"); // TypeError: Class constructor Player cannot be invoked without 'new'
super() in Derived Classes// ❌ WRONG - Using 'this' before calling super()
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name, breed) {
this.breed = breed; // ReferenceError: Must call super before accessing 'this'
super(name);
}
}
// ✓ CORRECT - Call super() first, then use 'this'
class Cat extends Animal {
constructor(name, color) {
super(name); // Initialize parent first
this.color = color; // Now 'this' is available
}
}
const kitty = new Cat("Whiskers", "orange");
console.log(kitty.name); // "Whiskers"
console.log(kitty.color); // "orange"
_underscore Means Private// ❌ WRONG - Underscore is just a naming convention
class BankAccount {
constructor(balance) {
this._balance = balance; // Not actually private!
}
getBalance() {
return this._balance;
}
}
const account = new BankAccount(1000);
console.log(account._balance); // 1000 - fully accessible!
account._balance = 999999; // Can be modified!
console.log(account.getBalance()); // 999999 - no protection!
// ✓ CORRECT - Use private fields (#) for true privacy
class SecureBankAccount {
#balance; // Truly private
constructor(balance) {
this.#balance = balance;
}
getBalance() {
return this.#balance;
}
}
const secure = new SecureBankAccount(1000);
// console.log(secure.#balance); // SyntaxError!
console.log(secure.getBalance()); // 1000 - only accessible via methods
this Incorrectly in Factory Functions// ❌ WRONG - 'this' in factory return object can cause issues
function createCounter() {
return {
count: 0,
increment() {
this.count++; // 'this' depends on how the method is called
}
};
}
const counter = createCounter();
counter.increment(); // Works
console.log(counter.count); // 1
const increment = counter.increment;
increment(); // 'this' is undefined or global!
console.log(counter.count); // Still 1 - didn't work!
// ✓ CORRECT - Use closure to avoid 'this' issues
function createSafeCounter() {
let count = 0; // Closure variable
return {
increment() {
count++; // No 'this' needed
},
getCount() {
return count;
}
};
}
const safeCounter = createSafeCounter();
const safeIncrement = safeCounter.increment;
safeIncrement(); // Works even when extracted!
console.log(safeCounter.getCount()); // 1
class Button {
count = 0;
// Arrow function automatically binds 'this' to the instance
handleClick = () => {
this.count++;
console.log(`Clicked ${this.count} times`);
};
}
const button = new Button();
const handler = button.handleClick;
handler(); // Works! 'this' is still bound to button
This is an alternative to manually binding methods with .bind(this) in the constructor.
</Tip>
| Aspect | Factory Function | ES6 Class |
|--------|-----------------|-----------|
| Syntax | Regular function returning object | `class` keyword |
| `new` keyword | Not required | Required |
| `instanceof` | Doesn't work | Works |
| Privacy | Closures (truly private) | Private fields `#` (truly private) |
| Memory | Each instance has own methods | Methods shared via prototype |
| `this` binding | Can avoid `this` entirely | Must use `this` |
```javascript
// Factory - just a function
function createUser(name) {
return { name, greet() { return `Hi, ${name}!` } }
}
// Class - a blueprint
class User {
constructor(name) { this.name = name }
greet() { return `Hi, ${this.name}!` }
}
const u1 = createUser("Alice") // No 'new'
const u2 = new User("Bob") // Requires 'new'
```
**Best answer:** Explain both syntax differences AND when to use each.
`new` performs 4 steps:
1. **Creates** a new empty object `{}`
2. **Links** its prototype to `Constructor.prototype`
3. **Executes** the constructor with `this` bound to the new object
4. **Returns** the object (unless constructor returns a different object)
```javascript
// This is essentially what 'new' does:
function myNew(Constructor, ...args) {
const obj = Object.create(Constructor.prototype)
const result = Constructor.apply(obj, args)
return (typeof result === 'object' && result !== null) ? result : obj
}
```
**Best answer:** Mention all 4 steps and show the simulation code.
Two ways to achieve **true** privacy:
**1. Private Fields (`#`) in Classes:**
```javascript
class BankAccount {
#balance = 0
deposit(amt) { this.#balance += amt }
getBalance() { return this.#balance }
}
// account.#balance → SyntaxError!
```
**2. Closures in Factory Functions:**
```javascript
function createBankAccount() {
let balance = 0
return {
deposit(amt) { balance += amt },
getBalance() { return balance }
}
}
// account.balance → undefined
```
**Not truly private:** The `_underscore` convention is just a naming hint. Those properties are fully accessible.
**Best answer:** Distinguish between the `_underscore` convention (not private) and the two truly private approaches.
Use **composition** when:
- You need to mix behaviors from multiple sources (a flying fish, a swimming bird)
- The "is-a" relationship doesn't make sense
- You want loose coupling between components
- You need flexibility to change behaviors at runtime
Use **inheritance** when:
- There's a clear "is-a" hierarchy (Dog is an Animal)
- You need `instanceof` checks
- You want to share implementation, not just interface
```javascript
// Inheritance problem: What about a penguin that can't fly?
class Bird { fly() {} }
class Penguin extends Bird { fly() { throw Error("Can't fly!") } } // Awkward!
// Composition solution: Mix behaviors
const canSwim = (state) => ({ swim() { /*...*/ } })
const canWalk = (state) => ({ walk() { /*...*/ } })
function createPenguin(name) {
const state = { name }
return { ...canSwim(state), ...canWalk(state) } // No fly!
}
```
**Best answer:** Give the "Gorilla-Banana" problem example and show composition code.
```javascript
class Player {
constructor(name) { this.name = name }
attack() { return `${this.name} attacks!` }
}
// Classes ARE functions!
console.log(typeof Player) // "function"
// Methods are on the prototype, not the instance
console.log(Player.prototype.attack) // [Function: attack]
```
This is why JavaScript has quirks like `this` binding issues that don't exist in true class-based languages.
- **True privacy** via closures (before `#` existed)
- **No `this` binding issues** when using closures
- **Return different types** based on input
- **No `new` keyword** to forget
```javascript
// Factory can return different types!
function createShape(type) {
if (type === 'circle') return { radius: 10, area() { /*...*/ } }
if (type === 'square') return { side: 10, area() { /*...*/ } }
}
// Classes always return instances of that class
```
The trade-off is memory efficiency (classes share methods via prototype).
| Aspect | `_underscore` | `#privateField` |
|--------|---------------|-----------------|
| Accessibility | Fully public | Truly private |
| Convention only? | Yes | No, enforced |
| Error on access | No error | SyntaxError |
```javascript
class Account {
_balance = 100 // Accessible! Just a convention
#pin = 1234 // Truly private
}
const acc = new Account()
console.log(acc._balance) // 100 — works!
// console.log(acc.#pin) // SyntaxError!
```
- **React:** Moved from class components to function components with hooks
- **Functional programming:** Favors factory functions and composition
- **Simplicity:** Factory functions have fewer footguns (`this`, `new`)
**Use classes when:** You need `instanceof`, clear hierarchies, or OOP familiarity.
**Use factories when:** You need composition, true privacy, or functional style.
extendsUse extends to create a class that inherits from another:
// Base class (parent)
class Character {
constructor(name, health) {
this.name = name;
this.health = health;
}
attack() {
return `${this.name} attacks!`;
}
takeDamage(amount) {
this.health -= amount;
return `${this.name} has ${this.health} HP left.`;
}
isAlive() {
return this.health > 0;
}
}
// Derived class (child)
class Warrior extends Character {
constructor(name) {
super(name, 150); // Call parent constructor
this.armor = 20; // Add new property
}
// Override parent method
takeDamage(amount) {
const reduced = Math.max(0, amount - this.armor);
return super.takeDamage(reduced); // Call parent method
}
// New method only for Warriors
shieldBash() {
return `${this.name} bashes with shield for ${this.armor} damage!`;
}
}
// Another derived class
class Mage extends Character {
constructor(name) {
super(name, 80); // Mages have less health
this.mana = 100;
}
// Override with different behavior
attack() {
if (this.mana >= 10) {
this.mana -= 10;
return `${this.name} casts fireball for 50 damage! (Mana: ${this.mana})`;
}
return `${this.name} is out of mana! Basic attack for 5 damage.`;
}
meditate() {
this.mana = Math.min(100, this.mana + 30);
return `${this.name} meditates. Mana: ${this.mana}`;
}
}
// Usage
const conan = new Warrior("Conan");
const gandalf = new Mage("Gandalf");
console.log(conan.attack()); // "Conan attacks!"
console.log(conan.takeDamage(30)); // "Conan has 140 HP left." (reduced by armor)
console.log(conan.shieldBash()); // "Conan bashes with shield for 20 damage!"
console.log(gandalf.attack()); // "Gandalf casts fireball for 50 damage! (Mana: 90)"
console.log(gandalf.meditate()); // "Gandalf meditates. Mana: 100"
// instanceof works through the chain
console.log(conan instanceof Warrior); // true
console.log(conan instanceof Character); // true
console.log(gandalf instanceof Mage); // true
console.log(gandalf instanceof Warrior); // false
super Keywordsuper does two things:
super(...))super.method())class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound.`;
}
}
class Dog extends Animal {
constructor(name, breed) {
// MUST call super() before using 'this' in derived class
super(name); // Calls Animal's constructor
this.breed = breed;
}
speak() {
// Call parent method and add to it
const parentSays = super.speak();
return `${parentSays} Specifically: Woof!`;
}
}
const rex = new Dog("Rex", "German Shepherd");
console.log(rex.speak());
// "Rex makes a sound. Specifically: Woof!"
class Child extends Parent {
constructor(name) {
// this.name = name; // ERROR! Can't use 'this' yet
super(); // Must call super first
this.name = name; // Now 'this' is available
}
}
Inheritance can become problematic with deep hierarchies:
// The "Gorilla-Banana Problem"
class Animal { }
class Mammal extends Animal { }
class Primate extends Mammal { }
class Ape extends Primate { }
class Gorilla extends Ape { }
// You wanted a banana, but you got the whole jungle!
// - Deep chains are hard to understand
// - Changes to parent classes can break children
// - Tight coupling between classes
Instead of inheritance ("is-a"), use composition ("has-a"):
// Define behaviors as small, focused functions
const canWalk = (state) => ({
walk() {
state.position += state.speed;
return `${state.name} walks to position ${state.position}`;
}
});
const canSwim = (state) => ({
swim() {
state.position += state.speed * 1.5;
return `${state.name} swims to position ${state.position}`;
}
});
const canFly = (state) => ({
fly() {
state.position += state.speed * 3;
return `${state.name} flies to position ${state.position}`;
}
});
const canSpeak = (state) => ({
speak(message) {
return `${state.name} says: "${message}"`;
}
});
// Compose characters by mixing behaviors
function createDuck(name) {
const state = { name, position: 0, speed: 2 };
return {
name: state.name,
...canWalk(state),
...canSwim(state),
...canFly(state),
...canSpeak(state),
getPosition: () => state.position
};
}
function createPenguin(name) {
const state = { name, position: 0, speed: 1 };
return {
name: state.name,
...canWalk(state),
...canSwim(state),
// No canFly! Penguins can't fly
...canSpeak(state),
getPosition: () => state.position
};
}
function createFish(name) {
const state = { name, position: 0, speed: 4 };
return {
name: state.name,
...canSwim(state),
// Fish can only swim
getPosition: () => state.position
};
}
// Usage
const donald = createDuck("Donald");
donald.walk(); // "Donald walks to position 2"
donald.swim(); // "Donald swims to position 5"
donald.fly(); // "Donald flies to position 11"
donald.speak("Quack!"); // 'Donald says: "Quack!"'
const tux = createPenguin("Tux");
tux.walk(); // Works
tux.swim(); // Works
// tux.fly(); // TypeError: tux.fly is not a function
const nemo = createFish("Nemo");
nemo.swim(); // Works
// nemo.walk(); // TypeError: nemo.walk is not a function
// nemo.fly(); // TypeError: nemo.fly is not a function
┌─────────────────────────────────────────────────────────────────────┐
│ INHERITANCE (is-a) │
│ │
│ Animal Problem: What about flying fish? │
│ │ What about penguins that can't fly? │
│ ├── Bird (can fly) What about bats (mammals that fly)? │
│ │ └── Penguin ??? │
│ ├── Fish (can swim) You end up with awkward hierarchies │
│ │ └── FlyingFish ??? or lots of override methods. │
│ └── Mammal │
│ └── Bat ??? │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ COMPOSITION (has-a) │
│ │
│ Behaviors: Characters: │
│ ┌─────────┐ ┌───────────────────────────────────────┐ │
│ │ canWalk │─────────│ Duck = canWalk + canSwim + canFly │ │
│ └─────────┘ │ Penguin = canWalk + canSwim │ │
│ ┌─────────┐ │ Fish = canSwim │ │
│ │ canSwim │─────────│ FlyingFish = canSwim + canFly │ │
│ └─────────┘ │ Bat = canWalk + canFly │ │
│ ┌─────────┐ └───────────────────────────────────────┘ │
│ │ canFly │ │
│ └─────────┘ Mix and match any combination! │
└─────────────────────────────────────────────────────────────────────┘
| Aspect | Inheritance | Composition |
|---|---|---|
| Relationship | "is-a" (Dog is an Animal) | "has-a" (Duck has flying ability) |
| Flexibility | Rigid hierarchy | Mix and match behaviors |
| Reuse | Through parent chain | Through behavior functions |
| Coupling | Tight (child depends on parent) | Loose (behaviors are independent) |
| Testing | Harder (need parent context) | Easier (test behaviors in isolation) |
| Best for | Clear hierarchies, instanceof needed | Flexible combinations, multiple behaviors |
| Feature | Factory Function | ES6 Class |
|---|---|---|
| Syntax | Regular function | class keyword |
new keyword | Not needed | Required |
instanceof | Doesn't work | Works |
| True privacy | Closures | Private fields (#) |
| Memory efficiency | Each instance has own methods | Methods shared via prototype |
this binding | Can avoid this with closures | Must be careful with this |
| Inheritance | Composition (flexible) | extends (hierarchical) |
| Familiarity | Functional style | OOP style (familiar to Java/C# devs) |
┌─────────────────────────────────────────────────────────────────────┐
│ WHICH SHOULD I USE? │
│ │
│ Do you need instanceof checks? │
│ YES ──► Use Class │
│ NO ──▼ │
│ │
│ Do you need a clear inheritance hierarchy? │
│ YES ──► Use Class with extends │
│ NO ──▼ │
│ │
│ Do you need to mix multiple behaviors? │
│ YES ──► Use Factory with composition │
│ NO ──▼ │
│ │
│ Do you need truly private data? │
│ YES ──► Either works (Factory closures OR Class with #) │
│ NO ──▼ │
│ │
│ Is your team familiar with OOP? │
│ YES ──► Use Class (more familiar syntax) │
│ NO ──► Use Factory (simpler mental model) │
└─────────────���───────────────────────────────────────────────────────┘
React Components (Classes → Functions)
// Old: Class components
class Button extends React.Component {
render() {
return <button>{this.props.label}</button>;
}
}
// Modern: Function components (like factories)
function Button({ label }) {
return <button>{label}</button>;
}
Game Entities (Classes for hierarchy)
class Entity { }
class Character extends Entity { }
class Player extends Character { }
class NPC extends Character { }
Utility Objects (Factories for flexibility)
const logger = createLogger({ level: 'debug', prefix: '[App]' });
const cache = createCache({ maxSize: 100, ttl: 3600 });
Factory functions are regular functions that return objects — simple and flexible
Constructor functions are used with new to create instances — the traditional approach
ES6 classes are syntactic sugar over constructors — cleaner syntax, same behavior
The new keyword creates an object, links its prototype, runs the constructor, and returns the result
Prototype methods are shared by all instances — saves memory
Private fields (#) provide true privacy in classes — can't be accessed from outside
Closures provide true privacy in factories — variables trapped in function scope
Static methods belong to the class itself, not instances — use for utilities and factory methods
Inheritance (extends) creates "is-a" relationships — use for clear hierarchies
Composition creates "has-a" relationships — more flexible than inheritance
Use classes when you need instanceof, clear hierarchies, or team familiarity
Use factories when you need composition, true privacy, or functional style
</Info>When you call `new Constructor(args)`:
1. **Create** a new empty object (`{}`)
2. **Link** the object's prototype to `Constructor.prototype`
3. **Execute** the constructor with `this` bound to the new object
4. **Return** the object (unless constructor returns a different object)
```javascript
function myNew(Constructor, ...args) {
const obj = Object.create(Constructor.prototype); // Steps 1-2
const result = Constructor.apply(obj, args); // Step 3
return (typeof result === 'object' && result !== null) ? result : obj; // Step 4
}
```
**Instance methods** are defined in the constructor — each instance gets its own copy:
```javascript
function Player(name) {
this.attack = function() { }; // Each player has own attack function
}
```
**Prototype methods** are shared by all instances — more memory efficient:
```javascript
Player.prototype.attack = function() { }; // All players share one function
```
In ES6 classes, methods defined in the class body are automatically prototype methods:
```javascript
class Player {
attack() { } // This goes on Player.prototype
}
```
| Aspect | Private Fields (#) | Closures |
|--------|-------------------|----------|
| Syntax | `this.#field` | `let variable` in factory |
| Error on access | SyntaxError | Returns `undefined` |
| Memory | Efficient (shared methods) | Each instance has own methods |
| `instanceof` | Works | Doesn't work |
```javascript
// Private Fields
class Wallet {
#balance = 0;
getBalance() { return this.#balance; }
}
// w.#balance throws SyntaxError
// Closures
function createWallet() {
let balance = 0;
return { getBalance() { return balance; } };
}
// w.balance returns undefined
```
`super()` calls the parent class's constructor. You **must** call it in a derived class constructor **before** using `this`.
```javascript
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name, breed) {
// this.breed = breed; // ERROR! Can't use 'this' yet
super(name); // Call parent constructor first
this.breed = breed; // Now 'this' is available
}
}
```
`super.method()` calls a parent's method from within an overriding method.
Use **composition** when:
- You need to mix behaviors from multiple sources (a flying fish, a swimming bird)
- The "is-a" relationship doesn't make sense
- You want loose coupling between components
- You need flexibility to change behaviors at runtime
Use **inheritance** when:
- There's a clear "is-a" hierarchy (Dog is an Animal)
- You need `instanceof` checks
- You want to share implementation, not just interface
**Rule of thumb:** "Favor composition over inheritance" — composition is more flexible.
Classes are called "syntactic sugar" because they don't add new functionality — they just provide a cleaner syntax for constructor functions and prototypes.
```javascript
// This class...
class Player {
constructor(name) { this.name = name; }
attack() { return `${this.name} attacks!`; }
}
// ...is equivalent to:
function Player(name) { this.name = name; }
Player.prototype.attack = function() { return `${this.name} attacks!`; };
// Both create the same result:
typeof Player === 'function' // true for both
```
The class syntax makes the code easier to read and write, but under the hood, JavaScript is still using prototypes.