JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
74: Mixins and Composition
For a long time, the "correct" way to teach object-oriented programming was to lean heavily on inheritance. You've already seen how extends works in JavaScript, and it feels intuitive at first. You have a base class, you extract the common logic, and you build specialized versions of that class. It’s a clean, logical tree. Or at least, it is for the first twenty minutes of a project.
The Inheritance Trap
Let's say we're building a game. We start with a Character class. Then we realize we need a Warrior and a Mage, so we extend Character into those two. Simple. But then the design doc changes. Now we need a Paladin—who is basically a Warrior that can also cast heals—and a BattleMage—who is a Mage that can swing a sword.
class Character {
constructor(name) { this.name = name; }
}
class Warrior extends Character {
attack() { console.log(`${this.name} swings a sword!`); }
}
class Mage extends Character {
castSpell() { console.log(`${this.name} casts a fireball!`); }
}
// Now what?
// class Paladin extends Warrior { ... } // But how do I get the Mage's healing?
// class Paladin extends Warrior, Mage { ... } // JavaScript doesn't allow this.
This is where most developers hit a wall. Since JavaScript doesn't support multiple inheritance, you're forced to either duplicate code (copying castSpell into the Paladin class) or push logic further up the chain into the Character base class. If you do the latter, you end up with a "fat" base class where every single character has every single ability, even if a basic Peasant shouldn't be able to cast a Level 9 Meteor spell. I've spent way too many hours untangling these "god objects" in legacy codebases.
Picking and Choosing with Mixins
The alternative is to stop thinking about what an object is and start thinking about what it does. This is the core of composition. Instead of a rigid hierarchy, we create small, focused pieces of functionality—mixins—that we can plug into any object regardless of its place in a class tree.
In JavaScript, a mixin is essentially just a function or an object that provides a set of methods. We can use Object.assign() to "mix" these capabilities into our class prototype or directly into an instance.
const canAttack = {
attack() { console.log(`${this.name} swings a weapon!`); }
};
const canCastSpells = {
castSpell() { console.log(`${this.name} casts a spell!`); }
};
const canHeal = {
heal() { console.log(`${this.name} restores some HP!`); }
};
class Character {
constructor(name) { this.name = name; }
}
class Paladin extends Character {}
Object.assign(Paladin.prototype, canAttack, canHeal);
class BattleMage extends Character {}
Object.assign(BattleMage.prototype, canAttack, canCastSpells);
const arthas = new Paladin("Arthas");
arthas.attack(); // Works
arthas.heal(); // Works
The Cost of Flexibility
Now, I'm not telling you that composition is a silver bullet. There are trade-offs here that you need to be aware of. The biggest issue is name collisions. If canAttack and canHeal both happened to have a method called init(), the one applied last via Object.assign would simply overwrite the first one. You lose the safety of the compiler (or the engine) telling you that you're overriding a method; it just happens silently.
You also lose some of the clarity of instanceof. While arthas instanceof Paladin still works, you can't easily check arthas instanceof canHeal because canHeal isn't a class—it's just a plain object. If your logic depends heavily on checking the "type" of an object, composition makes that harder. But in my experience, if you find yourself using instanceof constantly, you're usually fighting against the design of your system anyway.
The win here is agility. When the game designer decides that Dogs can now Heal, you don't have to restructure your entire animal kingdom hierarchy. You just plug in the canHeal mixin and move on with your day.
📋 Practical Task
Build a Modular Smart-Home Device System
You are tasked with creating a system for smart home devices. Instead of using a deep inheritance tree, use mixins and composition to build the following devices based on their capabilities.
Requirements:
- Create three mixin objects:
connectable: contains a methodconnect()that logs "Connecting to WiFi...".programmable: contains a methodsetSchedule(time)that logs "Schedule set for [time]".batteryPowered: contains a methodcheckBattery()that logs "Battery at 85%".
- Create a base class
Devicethat takes anamein the constructor. - Create three specific device classes that extend
Device:SmartBulb: Should beconnectableandprogrammable.SmartCamera: Should beconnectableandbatteryPowered.SmartThermostat: Should beconnectableandprogrammable.
- Instantiate one of each and call at least two of their mixed-in methods to verify they work.
There are no comments for now.