JavaScriptMixins

Mixins

JavaScript classes only support single inheritanceclass B extends A and that is the end of the chain. When you want to share a capability across many classes that otherwise have nothing in common, the answer is a mixin: a chunk of behaviour you splice into a class. There are two common ways to write one, and each has its place.

Pattern 1: copying methods onto the prototype

The simplest mixin is a plain object of methods. Object.assign copies them onto a class's prototype, and instances pick them up through normal inheritance.

JS
// A capability — sayHi to anyone with a name
const greeter = {
  greet() {
    return `Hi, I am ${this.name}`;
  },
  shout() {
    return this.greet().toUpperCase();
  },
};

class Person {
  constructor(name) { this.name = name; }
}

Object.assign(Person.prototype, greeter);

const p = new Person("Ada");
console.log(p.greet());   // 'Hi, I am Ada'
console.log(p.shout());   // 'HI, I AM ADA'

The methods become real members of Person.prototype. instanceof is unaffected because the class chain has not changed.

Pattern 2: class factories

A more powerful pattern: a function that takes a base class and returns a new class that extends it with the mixin's behaviour. You can chain factories to compose multiple capabilities.

JS
// Each mixin is a function: Base => class extends Base { ... }
const Serializable = Base => class extends Base {
  toJSON() {
    return Object.fromEntries(
      Object.entries(this).filter(([k]) => !k.startsWith("_"))
    );
  }
};

const Timestamped = Base => class extends Base {
  createdAt = new Date();
  age() { return Date.now() - this.createdAt.getTime(); }
};

// Stack them
class Doc extends Serializable(Timestamped(Object)) {
  constructor(title) {
    super();
    this.title = title;
    this._secret = "internal";
  }
}

const d = new Doc("hello");
console.log(d.toJSON());        // { createdAt: ..., title: 'hello' }
console.log(d.age() >= 0);      // true

Each factory builds a real anonymous class in the prototype chain. Because they extend through super, instance methods compose cleanly and instanceof still works for the outermost class.

Mixins with state

Class-factory mixins can carry their own fields and even constructors — they just have to call super so the chain stays valid.

JS
const Counted = Base => class extends Base {
  static instances = 0;
  constructor(...args) {
    super(...args);
    this.constructor.instances++;
  }
};

class Widget extends Counted(Object) {}
class Gadget extends Counted(Object) {}

new Widget(); new Widget(); new Gadget();
console.log(Widget.instances);   // 2
console.log(Gadget.instances);   // 1
Naming collisions

Mixins compose — but what happens if two of them define the same method?

  • In the prototype-copy pattern: later Object.assign wins. The last mixin overwrites earlier ones silently.

  • In the class-factory pattern: the innermost mixin defines the method first, each outer one can call super.method() to keep the chain alive.

  • When in doubt, name your mixin methods to avoid collisions, or expose a single well-named entry point.

Don't override silently
Whichever pattern you pick, document what the mixin adds. A future reader who searches for "where is this method defined" should be able to find the mixin — not stare at a class body that does not contain the method.
Mixins vs inheritance vs composition
  • Use inheritance when one class genuinely is a kind of another and shares its lifecycle and identity.

  • Use mixins when several unrelated classes need the same capability bolted on.

  • Use composition when behaviour is owned by a delegated object — this.logger = new Logger() is often simpler and easier to test than mixing the logger in.

  • Prefer composition when in doubt. Reach for mixins for cross-cutting concerns that would otherwise be duplicated across many classes.

A small real-world flavour

JS
const EventEmitter = Base => class extends Base {
  #listeners = new Map();

  on(event, fn) {
    if (!this.#listeners.has(event)) this.#listeners.set(event, []);
    this.#listeners.get(event).push(fn);
    return this;
  }

  emit(event, ...args) {
    for (const fn of this.#listeners.get(event) ?? []) fn(...args);
  }
};

class Button extends EventEmitter(Object) {
  click() { this.emit("click"); }
}

const btn = new Button();
btn.on("click", () => console.log("clicked!"));
btn.click();
clicked!
JavaScript already has interfaces
If you only need a *contract* — every shape should support `.toJSON()`, say — there is no need for a mixin. JavaScript is duck-typed; a free function or a TypeScript interface is usually enough. Reach for mixins when behaviour itself has to be shared.
One sentence
A mixin is a recipe for adding behaviour without changing the inheritance chain — either copy the methods onto a prototype, or use a class factory that extends whatever base you pass in.