1 — Why JavaScript Has No Multiple Inheritance
Session 05 showed extends linking one class to one parent. What if a Duck needs behaviour from both Swimmer and Flyer? Many languages let a class inherit from multiple parents directly. JavaScript deliberately doesn't — class X extends A, B is not valid syntax, full stop.
This isn't an oversight. Multiple inheritance creates a famous headache called the "diamond problem": if both Swimmer and Flyer defined a method called move(), which one wins when Duck inherits from both? Different languages answer this differently, and all the answers are at least a little confusing. JavaScript's prototype chain is a single, simple line — one class, one parent, no ambiguity — and the language sticks to that by design.
class Swimmer { swim() { return 'swimming'; } } class Flyer { fly() { return 'flying'; } } // class Duck extends Swimmer, Flyer { } // SyntaxError — not real JavaScript
So how does a Duck get both abilities? Not through inheritance at all — through composition (Session 05's "has-a" idea) and a specific composition technique called mixins.
2 — Mixin Functions
A mixin is a function that takes a class and returns a new class with extra methods bolted on. You "mix in" behaviour rather than inherit it from a single rigid parent — and you can mix in as many as you want, in any combination.
// Each mixin takes a "base" class and returns an extended version of it const Swimmer = (Base) => class extends Base { swim() { return `${this.name} is swimming`; } }; const Flyer = (Base) => class extends Base { fly() { return `${this.name} is flying`; } }; class Animal { constructor(name) { this.name = name; } } // Apply both mixins by wrapping Animal, one call at a time: class Duck extends Flyer(Swimmer(Animal)) {} const duck = new Duck('Donald'); console.log(duck.swim()); // "Donald is swimming" console.log(duck.fly()); // "Donald is flying"
Read Flyer(Swimmer(Animal)) from the inside out: Animal is the foundation, Swimmer(...) wraps it with swimming behaviour, then Flyer(...) wraps that result with flying behaviour. Each mixin is just an ordinary extends relationship under the hood (back to Session 05) — the trick is that the "parent" being extended is computed dynamically by a function instead of being one fixed, hardcoded class name.
Crucially, this dodges the diamond problem entirely: there's a strict linear order (Animal → Swimmer's layer → Flyer's layer), not two parents merged simultaneously. If both mixins defined the same method name, whichever is applied last (outermost) simply overrides the other — same rule as any normal override from Session 05, no special-case ambiguity.
3 — Composing Behaviour Over extends Chains
Mixins are one composition technique built on top of extends. But the broader, more common idea — and the one worth defaulting to — is plain composition: instead of stretching an inheritance chain to cover every combination of behaviour, give a class properties that are themselves small, focused objects, each responsible for one thing.
// Each behaviour is its own small, focused, independently testable object const swimBehavior = { swim() { return 'swimming'; } }; const flyBehavior = { fly() { return 'flying'; } }; const walkBehavior = { walk() { return 'walking'; } }; class Duck { constructor(name) { this.name = name; // compose exactly the behaviours THIS duck needs Object.assign(this, swimBehavior, flyBehavior, walkBehavior); } } class Penguin { constructor(name) { this.name = name; // no flyBehavior here — penguins don't fly, and nothing forces them to inherit it Object.assign(this, swimBehavior, walkBehavior); } } const duck = new Duck('Donald'); const penguin = new Penguin('Pingu'); console.log(typeof penguin.fly); // "undefined" — correctly has no fly() at all
Compare this to forcing every flightless bird through an inheritance chain designed around flying animals — you'd end up overriding fly() to throw an error, or adding awkward "can I fly?" flags. Composition just doesn't give an object a behaviour it doesn't need in the first place. This directly echoes the engine/car example from Session 05: favor "has this behaviour" over "is forced into a hierarchy that assumes this behaviour."
4 — Object.assign() for Mixins
Topic 3's Object.assign() call is itself a lightweight mixin technique, distinct from the class-wrapping mixins in Topic 2 — worth knowing as a separate tool, since it's simpler when you don't need the mixed-in behaviour to participate in instanceof checks or the prototype chain at all.
const serializable = { toJSON() { return JSON.stringify(this); } }; class Product { constructor(name, price) { this.name = name; this.price = price; } } Object.assign(Product.prototype, serializable); // ^ mixed into the PROTOTYPE here, so it's shared across instances // (same memory-efficiency idea from Session 02), instead of copied // onto each instance individually like Topic 3's example did const p = new Product('Keyboard', 80); console.log(p.toJSON()); // '{"name":"Keyboard","price":80}' // Trade-off vs Topic 2's class-wrapping mixins: // Object.assign() mixes in PLAIN properties/methods — no constructor logic, // no participation in `instanceof` for some "Serializable" type. Reach for // the class-wrapping mixin pattern (Topic 2) when you need that; reach for // Object.assign() when you just need to attach some methods quickly.
Four tools, one underlying philosophy from this session: when a single extends chain can't comfortably express what you need, reach for composition — mixin functions wrapping classes (Topic 2), plain object composition (Topic 3), or Object.assign() onto an instance or a prototype (Topic 4) — rather than forcing an awkward, deep, or multiple inheritance hierarchy that JavaScript doesn't support natively anyway.
15-Minute Review — Session 09
Stop the session timer and switch to the review timer in the sidebar. Answer these questions from memory — then check.
Q1 — Why doesn't JavaScript support class Duck extends Swimmer, Flyer?
Q2 — In class Duck extends Flyer(Swimmer(Animal)) {}, what is a mixin function like Swimmer actually doing?
Q3 — Why does giving Penguin only swimBehavior and walkBehavior (and not flyBehavior) avoid a problem that inheritance might cause?
Q4 — What's the key difference between mixing behaviour into a single instance vs into a class's .prototype using Object.assign()?