Mixins
JavaScript gives every object exactly one [[Prototype]], and a class can extend exactly one other class. That single-parent rule is clean, but it bites the moment you want a class to be two things at once.
Say you have a Camera class and a Phone class, and you dream up a CameraPhone that is both. Inheritance can’t express that — you’d have to pick one parent and re-implement the other. Or take a more practical case: you have a Guest class, and separately an EventEmitter that knows how to broadcast events. You want Guest to gain that event-broadcasting skill without giving up whatever it already inherits.
The pattern that solves this is called a mixin.
Borrowing the Wikipedia definition: a mixin is a class holding methods that other classes can use without inheriting from it. Put differently, a mixin packages up a slice of behavior. You never instantiate it on its own — you stir it into other classes to give them that behavior.
A mixin example
The plainest way to build a mixin in JavaScript is to make an object full of useful methods, then merge those methods into the prototype of whatever class needs them.
Here greetMixin gives Guest the ability to speak:
// mixin
let greetMixin = {
greet() {
alert(`Welcome ${this.name}`);
},
farewell() {
alert(`Goodbye ${this.name}`);
}
};
// usage:
class Guest {
constructor(name) {
this.name = name;
}
}
// copy the methods
Object.assign(Guest.prototype, greetMixin);
// now Guest can greet
new Guest("Maya").greet(); // Welcome Maya
No inheritance happens here at all. Object.assign walks the mixin’s own enumerable properties and copies them onto Guest.prototype, so afterward greet and farewell live directly on the prototype as if you’d written them there.
Try it below. The Guest class defines nothing but a constructor — every method the buttons call was copied in from the mixin. Change the name and watch this.name resolve on the instance:
Because nothing was inherited, the [[Prototype]] slot stays free. That means Guest can extend a real parent class and pick up the mixin at the same time:
class Guest extends Person {
// ...
}
Object.assign(Guest.prototype, greetMixin);
Guest inherits from Person through the prototype chain, and gains the mixin’s methods by direct copy. The two mechanisms don’t compete.
Mixins can use inheritance internally
A mixin is an object, and objects can have their own prototype. So a mixin can inherit from another mixin. Here greetMixin builds on top of speakMixin:
let speakMixin = {
speak(phrase) {
alert(phrase);
}
};
let greetMixin = {
__proto__: speakMixin, // (or we could use Object.setPrototypeOf to set the prototype here)
greet() {
// call parent method
super.speak(`Welcome ${this.name}`); // (*)
},
farewell() {
super.speak(`Goodbye ${this.name}`); // (*)
}
};
class Guest {
constructor(name) {
this.name = name;
}
}
// copy the methods
Object.assign(Guest.prototype, greetMixin);
// now Guest can greet
new Guest("Maya").greet(); // Welcome Maya
The interesting part is the super.speak(...) calls marked (*). You might expect super to reach into Guest’s parent — but Guest has no parent here. So where does super.speak find speak?
Look at the picture below, focusing on the right side:
The reason is [[HomeObject]]. When you write a method with the shorthand syntax (greet() { ... } inside an object literal), the engine permanently records where that method was born in a hidden [[HomeObject]] slot. For greet and farewell, that home is greetMixin, because that’s the literal they were written in.
Copying the methods with Object.assign doesn’t change [[HomeObject]]. The functions still remember greetMixin as home, even sitting on Guest.prototype.
And super.method always resolves as [[HomeObject]].[[Prototype]].method. So super.speak looks up greetMixin.[[Prototype]], which is speakMixin — and finds speak there. The lookup follows the mixin’s chain, not the class’s.
EventMixin
Let’s build a mixin you’d actually reach for. Many browser objects can generate events — a clean way to shout “something happened” to any code that cares, without those listeners knowing anything about each other. We’ll package that ability into a mixin so any class can gain it.
The mixin exposes three methods:
.trigger(name, [...data])— fires an event.nameidentifies the event; anything after it is data passed along to listeners..on(name, handler)— registershandlerto run whenever an event with thatnamefires. It receives the arguments from the matching.triggercall..off(name, handler)— unregisters that specifichandler.
Once mixed in, a player object could trigger("play") when a song starts, and a separate scrobbler object could on("play", ...) to record what was heard. Or a playlist fires "play" when a track is chosen, and unrelated code reacts. The emitter and the listeners stay decoupled.
Here’s the implementation:
let eventMixin = {
/**
* Subscribe to event, usage:
* playlist.on('play', function(track) { ... }
*/
on(eventName, handler) {
if (!this._eventHandlers) this._eventHandlers = {};
if (!this._eventHandlers[eventName]) {
this._eventHandlers[eventName] = [];
}
this._eventHandlers[eventName].push(handler);
},
/**
* Cancel the subscription, usage:
* playlist.off('play', handler)
*/
off(eventName, handler) {
let handlers = this._eventHandlers?.[eventName];
if (!handlers) return;
for (let i = 0; i < handlers.length; i++) {
if (handlers[i] === handler) {
handlers.splice(i--, 1);
}
}
},
/**
* Generate an event with the given name and data
* this.trigger('play', data1, data2);
*/
trigger(eventName, ...args) {
if (!this._eventHandlers?.[eventName]) {
return; // no handlers for that event name
}
// call the handlers
this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
}
};
Walking through the pieces:
.on(eventName, handler)— attacheshandlerto run when the named event occurs. The handlers live in an_eventHandlersproperty, an object mapping each event name to an array of functions. The first subscription lazily creates that store, so nothing is set up until it’s needed..off(eventName, handler)— findshandlerin the list and splices it out. Thei--after the splice matters: removing an item shifts everything left, so the loop steps the index back to avoid skipping the next handler..trigger(eventName, ...args)— runs every handler registered for that name. Each is called withhandler.apply(this, args), so inside a handlerthisis the emitting object and the arguments are whatevertriggerwas given after the name.
Here’s the flow of a triggered event, end to end:
Usage:
// Make a class
class Playlist {
play(track) {
this.trigger("play", track);
}
}
// Add the mixin with event-related methods
Object.assign(Playlist.prototype, eventMixin);
let playlist = new Playlist();
// add a handler, to be called when a track plays:
playlist.on("play", track => alert(`Now playing: ${track}`));
// triggers the event => the handler above runs and shows:
// Now playing: Nocturne
playlist.play("Nocturne");
Playlist.play doesn’t know or care who’s listening — it just calls this.trigger("play", track). Any code that wants to react registers with playlist.on("play", ...). You can add that reaction from anywhere, and you can add as many classes’ worth of event behavior as you like, all without touching their inheritance chains.
The demo below wires up the exact eventMixin from above onto a Playlist. Choosing a track fires play; the two listeners react independently, and you can subscribe or unsubscribe each one with .on / .off while the playlist keeps working, oblivious to who is watching:
Summary
Mixin is a general object-oriented term: a class (or, in JavaScript, a plain object) that supplies methods for other classes to use.
Some languages allow a class to inherit from several parents. JavaScript does not — one [[Prototype]], one extends. Mixins fill that gap by copying methods into a prototype rather than inheriting them, so a class can pick up several independent behaviors on top of its single real parent. Event handling, as shown above, is a textbook use.
The one real hazard: copying methods can silently overwrite a method the class already had if the names collide. Name mixin methods deliberately, ideally with a recognizable prefix or a namespace, to keep the odds of a clash low.