Mixins for “classes” in JavaScript

  • Tutorial
The same code in several places is a pain. Today I will write a few words about repeating pieces of classes. People have long come up with a solution - you can put the same methods and properties into a common base class, and if there is none, use impurities. There are a million implementations of this pattern for JavaScript, I want to elaborate on the approach when the mixin falls into the inheritance chain.

The problem is in the pictures


Let's start by visualizing our problem. Suppose we have two base classes and two child classes are inherited from them.



At some point in the child classes the need for the same functionality appears. The usual copy-paste will look like this on our diagram:



It very often happens that this functionality has nothing to do with the parent classes, so putting it into some base class is illogical and wrong. Take it out to a separate place - mixin. From a language point of view, a mixin can be a common object.



Now let's discuss the moment for which the whole article was written - how to properly knead our mixin into classes.

Based on my own experience, I can say that the most convenient way is to create a temporary class based on a mixin and substituting it in the inheritance queue.



Pros of this approach


  • ease of implementation;
  • ease of redefinition of the code contained in the mixin;
  • flexibility to connect mixins, the ability to create dependent mixins without much difficulty;
  • the use of another pattern in the code does not complicate its understanding and support, because the existing inheritance mechanism is used;
  • speed of intervention - not a single cycle is required to knead a mixin in this way;
  • optimal memory usage - you do not copy anything

Writing a code


In all subsequent examples, a specific implementation will be used - the Backbone.Mix library . After looking at the code, you will find that it is extremely simple, so you can easily adapt it for your favorite framework.

Let's see how to use mixins that are built into the inheritance chain in real life and experience the advantages of this approach in practice. Imagine that you are writing a site)) and on your site there are different things that you can close - popups, hints, etc. They should all listen to a click on an element with a CSS class closeand hide the element. A mixin for this might look like this:

var Closable = {
   events: function () {
       return {
           'click .close': this._onClickClose
       };
   },
   _onClickClose: function () {
       this.$el.hide();
   }
};

We intervene !!!


var Popup = Backbone.View.mix(Closable).extend({
   // что-то невероятное здесь
});

Pretty simple, isn't it? Now our inheritance chain looks like this:



  • first comes the base class Backbone.View
  • an anonymous class is inherited from it, the prototype of which is a mixin Closable
  • completes our chain Popup

Such a scheme makes it very easy to redefine and redefine methods from the mixin in the class to which it is mixed. For example, you can make it Popupwrite something to the console when closing:

var Popup = Backbone.View.mix(Closable).extend({
   _onClickClose: function () {
       this._super();
       console.log('Popup closed');
   }
});

Hereinafter, the examples use the backbone-super library

Impurities that do not interfere ..


... but they help. Sometimes kneading comes out not frail, and one mixin is indispensable. For example, imagine that we are cool guys and write a log in IndexedDB, and we also have our own mixin for this - Loggable:)

var Loggable = {
   _log: function () {
       // пишем в IndexedDB
   }
};

Then we’ll interfere with two mixins by the popup:

var Popup = Backbone.View.mix(Closable, Loggable).extend({
   _onClickClose: function () {
       this._super();
       this._log('Popup closed');
   }
});

The syntax is not complicated. On the diagram, it will look like this:



As you can see, the inheritance chain will line up depending on the order of connecting mixins.

Dependent Mixins


Now imagine a situation that our analyst approaches us and says that he wants to collect statistics on all closings of popups, hints - everything that can be closed. Of course, we have long had a mixin Trackablefor such cases, since the time we did the registration on the site.

var Trackable = {
   _track: function (event) {
       // отсылаем событие в какую-нибудь систему сбора аналитики
   }
};

No wonder that we want to connect Trackableand Closable, more precisely, Closableshould depend on Trackable. In our diagram, it will look like this:



And it Trackableshould appear earlier in the inheritance chain than Closable:



The code for mixins with dependencies will get a little more complicated:

var Closable = new Mixin({
   dependencies: [Trackable]
}, {
   events: function () {
       return {
           'click .close': this._onClickClose
       };
   },
   _onClickClose: function () {
       this.$el.hide();
       this._track('something closed'); // <- появившаяся функциональность
   }
});

But it’s not much more complicated, just now we have a place where you can write dependencies. To do this, we had to introduce an additional wrapper class - Mixin, and the mixin itself is now not just an object, but an instance of this class. It should be noted that the mixin connection itself does not change in this case:

var Popup = Backbone.View.mix(Closable, Loggable).extend({ … });

Document mixins correctly


WebStorm has great mixin support. It is enough to write JSDoc correctly , and the hints, autocomplete, understanding of the general structure of the code by the environment will significantly improve. The environment understands the tags @mixinand @mixes. Let's look at an example of a documented mixin Closableand class Popup.

/**
* @mixin Closable
* @mixes Trackable
* @extends Backbone.View
*/
var Closable = new Mixin({
   dependencies: [Trackable]
}, /**@lends Closable*/{
   /**
    * @returns {object.}
    */
   events: function () {
       return {
           'click .close': this._onClickClose
       };
   },
   /**
    * @protected
    */
   _onClickClose: function () {
       this.$el.hide();
       this._track('something closed');
   }
});
/**
* @class Popup
* @extends Backbone.View
* @mixes Closable
* @mixes Loggable
*/
var Popup = Backbone.View.mix(Closable, Loggable).extend({
/**
* @protected
*/
_onClickClose: function () {
       this._super();
       this._log('Popup closed');
   }
});

Very often, a mixin is written for classes that have a specific ancestor. Ours Closable, written for classes inherited from Backbone.View- is by no means an exception. In such a situation, the environment will not understand where in the mixin code the method calls of this ancestor are found, unless it is explicitly indicated @extends:

/**
* @mixin Closable
* @mixes Trackable
* @extends Backbone.View
*/
var Closable = new Mixin(...);

On this, perhaps all, a happy intervention!

English version on my blog. Backbone.Mix
library. Another code from the same authors: backbonex. What to do with jQuery noodles to bring it to a look when you can think about mixins? In english Immediately My Twitter code (only about the code)



Also popular now: