Inheritance couples a subclass to its parent's whole public surface and to its internals: later changes to the parent can break the child, and the child inherits methods that make no sense for it. Composition keeps the dependency narrow: a class holds another object (ideally behind a private field), delegates to it, and exposes only what it chooses. It's also easier to change at runtime and to test with a fake.
My recommendation:
- Default to composition (has-a). Pass dependencies into the constructor.
- Use
extendswhen the child really is a substitutable kind of the parent, the hierarchy is shallow, and the parent was written to be extended.Errorsubclasses and framework base classes are the usual cases. - Use a mixin only when several unrelated classes need the same small capability, and it adds methods without needing much of the host. Prefer a plain helper function or a composed collaborator when that works.
JavaScript has single inheritance, so sharing behaviour across hierarchies is what mixins are for. The pattern that works with classes is the subclass factory: const M = (Base) => class extends Base { ... }. It keeps the prototype chain linear, super works, and fields and private names behave normally. Apply it as class User extends Serializable(Entity).
Costs to know: each application creates a new class, so instanceof Serializable is unreliable. Names collide silently (the outermost wins) and mixins stack in an order that matters. They also depend on host members, which TypeScript or documentation must spell out. A stack of three or four mixins is a design smell: switch to composition.