Mixins

In JavaScript we can only inherit from a single object. There can be only one [[Prototype]] for an object. And a class may extend only one other class.

But sometimes that feels limiting. For instance, I have a class StreetSweeper and a class Bicycle, and want to make a StreetSweepingBicycle.

Or, talking about programming, we have a class Renderer that implements templating and a class EventEmitter that implements event handling, and want to merge these functionalities together with a class Page, to make a page that can use templates and emit events.

There’s a concept that can help here, called “mixins”.

As defined in Wikipedia, a mixin is a class that contains methods for use by other classes without having to be the parent class of those other classes.

In other words, a mixin provides methods that implement a certain behavior, but we do not use it alone, we use it to add the behavior to other classes.

A mixin example

The simplest way to make a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class.

For instance here the mixin sayHiMixin is used to add some “speech” for User:

// mixin
let sayHiMixin = {
  sayHi() {
    alert(`Hello ${this.name}`);
  },
  sayBye() {
    alert(`Bye ${this.name}`);
  }
};

// usage:
class User {
  constructor(name) {
    this.name = name;
  }
}

// copy the methods
Object.assign(User.prototype, sayHiMixin);

// now User can say hi
new User("Dude").sayHi(); // Hello Dude!

There’s no inheritance, but a simple method copying. So User may extend some other class and also include the mixin to “mix-in” the additional methods, like this:

class User extends Person {
  // ...
}

Object.assign(User.prototype, sayHiMixin);

Mixins can make use of inheritance inside themselves.

For instance, here sayHiMixin inherits from sayMixin:

let sayMixin = {
  say(phrase) {
    alert(phrase);
  }
};

let sayHiMixin = {
  __proto__: sayMixin, // (or we could use Object.create to set the prototype here)

  sayHi() {
    // call parent method
    super.say(`Hello ${this.name}`);
  },
  sayBye() {
    super.say(`Bye ${this.name}`);
  }
};

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

// copy the methods
Object.assign(User.prototype, sayHiMixin);

// now User can say hi
new User("Dude").sayHi(); // Hello Dude!

Please note that the call to the parent method super.say() from sayHiMixin looks for the method in the prototype of that mixin, not the class.

That’s because methods from sayHiMixin have [[HomeObject]] set to it. So super actually means sayHiMixin.__proto__, not User.__proto__.

EventMixin

Now let’s make a mixin for real life.

The important feature of many objects is working with events.

That is: an object should have a method to “generate an event” when something important happens to it, and other objects should be able to “listen” to such events.

An event must have a name and, optionally, bundle some additional data.

For instance, an object user can generate an event "login" when the visitor logs in. And another object calendar may want to receive such events to load the calendar for the logged-in person.

Or, a menu can generate the event "select" when a menu item is selected, and other objects may want to get that information and react on that event.

Events is a way to “share information” with anyone who wants it. They can be useful in any class, so let’s make a mixin for them:

let eventMixin = {
  /**
   * Subscribe to event, usage:
   *  menu.on('select', function(item) { ... }
  */
  on(eventName, handler) {
    if (!this._eventHandlers) this._eventHandlers = {};
    if (!this._eventHandlers[eventName]) {
      this._eventHandlers[eventName] = [];
    }
    this._eventHandlers[eventName].push(handler);
  },

  /**
   * Cancel the subscription, usage:
   *  menu.off('select', handler)
   */
  off(eventName, handler) {
    let handlers = this._eventHandlers && this._eventHandlers[eventName];
    if (!handlers) return;
    for (let i = 0; i < handlers.length; i++) {
      if (handlers[i] === handler) {
        handlers.splice(i--, 1);
      }
    }
  },

  /**
   * Generate the event and attach the data to it
   *  this.trigger('select', data1, data2);
   */
  trigger(eventName, ...args) {
    if (!this._eventHandlers || !this._eventHandlers[eventName]) {
      return; // no handlers for that event name
    }

    // call the handlers
    this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
  }
};

There are 3 methods here:

  1. .on(eventName, handler) – assigns function handler to run when the event with that name happens. The handlers are stored in the _eventHandlers property.
  2. .off(eventName, handler) – removes the function from the handlers list.
  3. .trigger(eventName, ...args) – generates the event: all assigned handlers are called and args are passed as arguments to them.

Usage:

// Make a class
class Menu {
  choose(value) {
    this.trigger("select", value);
  }
}
// Add the mixin
Object.assign(Menu.prototype, eventMixin);

let menu = new Menu();

// call the handler on selection:
menu.on("select", value => alert(`Value selected: ${value}`));

// triggers the event => shows Value selected: 123
menu.choose("123"); // value selected

Now if we have the code interested to react on user selection, we can bind it with menu.on(...).

And the eventMixin can add such behavior to as many classes as we’d like, without interfering with the inheritance chain.

Summary

Mixin – is a generic object-oriented programming term: a class that contains methods for other classes.

Some other languages like e.g. python allow to create mixins using multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying them into the prototype.

We can use mixins as a way to augment a class by multiple behaviors, like event-handling as we have seen above.

Mixins may become a point of conflict if they occasionally overwrite native class methods. So generally one should think well about the naming for a mixin, to minimize such possibility.

Tutorial map

Comments

read this before commenting…
  • You're welcome to post additions, questions to the articles and answers to them.
  • To insert a few words of code, use the <code> tag, for several lines – use <pre>, for more than 10 lines – use a sandbox (plnkr, JSBin, codepen…)
  • If you can't understand something in the article – please elaborate.