Understanding Mixin in JavaScript

Understanding Mixin in JavaScript

·

3 min read

According to wiki, in object-oriented programming languages, a mixin is a class that contains methods for use by other classes without having to be the parent class of those other classes.

For example, we have a class A and another class called Independence.

class A {
    // ...
}

class Independence {
    // ...
}

The class Independence contains some methods. If we want to use this methods inside the class A, the obvious option would be make class A inherit all these methods from class Independence.

class A extends Independence {
    // ...
}

This should work. But sometimes we don't want this inheritance. Maybe because class A is already extends from other class, or may be this class Independence's methods are all pretty independent, we don't want to mix them up. In this case, we could make use of this mixin technique.

To achieve mixin in JavaScript, we make use of the prototype property. Specifically, we can copy all methods from class Independence into class A. So A could have all the functionalities of class Independence. This process is like below.

function merge(target, source) {
    for (const name of Object.getOwnPropertyNames(source)) {
        // note here, we don't copy methods target already have
        if (target.hasOwnProperty(name)) continue;
        const descriptor = Object.getOwnPropertyDescriptor(source, name);
        Object.defineProperty(target, name, descriptor);
    }
}

// copy all methods
merge(A.prototype, Independence.prototype);

One practical example is to add event emitter for the class.

For example, we have a class EventEmitter and a class A, we want to add the functionalities of this EventEmitter into class A. Below code shows the whole process of using mixin to make it work.

class EventEmitter {
    on(name, cb) {
        this.handlers = this.handlers || {};
        this.handlers[name] = this.handlers[name] || [];
        this.handlers[name].push(cb);
    }
    off(name, cb) {
        if (!this.handlers || !this.handlers[name]) return;
        this.handlers[name] = this.handlers[name].filter(handler => handler !== cb);
    }
    emit(name, ...args) {
        if (!this.handlers || !this.handlers[name]) return;
        this.handlers[name].forEach(handler => handler(...args));
    }
}

class A {

}

function merge(target, source) {
    for (const name of Object.getOwnPropertyNames(source)) {
        if (target.hasOwnProperty(name)) continue;
        const descriptor = Object.getOwnPropertyDescriptor(source, name);
        Object.defineProperty(target, name, descriptor);
    }
}

merge(A.prototype, EventEmitter.prototype)

const a = new A();
const cb = () => console.log("hello")
a.on("hello", cb);
a.emit("hello");
a.off("hello", cb);
a.emit("hello");

Instead of changing prototype by hand, we can implement mixin with ES6 syntax.

The idea is to create a mixin builder function, with the ES6 extends syntax. Code example is like below.

let MyMixin = (superclass) => class extends superclass {
  // mixin methods here
};

class MyClass extends MyMixin(MySuperClass) {
  // class methods here, go ahead, use super!
}

There is a package called mixwith.js, which is pretty neat tool for this. With this tool, we can use mixin classes as below.

class MyClass extends mix(MySuperClass).with(MyMixin, OtherMixin) {
  // class methods here, go ahead, use super!
}

As you can see, the MySuperClass is the real super class we want to inherit, the MyMixin and OtherMixin is the mixin classes we want to combine with.