📜 ⬆️ ⬇️

Mixins for “classes” in javascript

The same code in several places is a pain. Today I’ll write a few words about repeating classes. People have come up with a solution a long time ago - 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.

Problem in pictures


Let's start with the visualization of our problem. Suppose we have two base classes and two child classes inherit from them.


')
At some point in the child classes there is a need for the same functionality. Normal copy-paste will look like this on our scheme:



It often happens that this functionality has nothing to do with the parent classes, therefore, it is illogical and wrong to bring it into some base class. We put it in a separate place - mixin. In terms of language, mixin can be a regular object.



And now let's discuss the moment for which the entire article is written - how to mix our mixin in classes correctly.

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



Advantages of this approach



Write the code


All subsequent examples will use the specific implementation - the Backbone.Mix library. By looking at the code, you will find that it is extremely simple, so you can easily adapt it to your favorite framework.

Let's take a look at how to apply mixins that are embedded in the chain of inheritance in real life and experience the advantages of this approach in practice. Imagine that you are writing a site)) and there are different things on your site that can be closed - pop-ups, hints, etc. All of them have to listen to click on the element with the CSS class close and hide the element. 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:




Such a scheme makes it very easy to redefine and pre-define methods from mixin in the class to which it is mixed. For example, you can make Popup write something to the console when it is closed:

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

Here and below, the examples use the backbone-super library.

Impurities that do not interfere ..


... and help. Sometimes the batch is 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 Loggable - Loggable :)

 var Loggable = { _log: function () { //   IndexedDB } }; 

Then to the popup, we will interfere with two mixins:

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

The syntax seems to be not complicated. On the diagram, it will look like this:



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

Dependent mixins


Now imagine a situation that our analyst approaches us and says that he wants to collect statistics on all closures of pop-ups, hints - everything that can be closed. Of course, we have long been Trackback Trackable for such cases, from the time we did the registration on the site.

 var Trackable = { _track: function (event) { //    -    } }; 

No wonder that we want to link Trackable and Closable , or rather, Closable should depend on Trackable . In our diagram, it will look like this:



And in the inheritance chain, the Trackable should be earlier than Closable :



The code for mixin with dependencies will be a bit 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 became not so much more difficult, just now we have a place where we can write dependencies. For 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 excellent mixin support. It is enough just to write JSDoc correctly, and the autocomplete hints, understanding by the environment of the general structure of the code will noticeably improve. The medium understands the @mixin and @mixes . Let's look at an example of the documented mixin Closable and class Popup .

 /** * @mixin Closable * @mixes Trackable * @extends Backbone.View */ var Closable = new Mixin({ dependencies: [Trackable] }, /**@lends Closable*/{ /** * @returns {object.<function(this: Closable, e: jQuery.Event)>} */ 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, mixin is written for classes that have a specific ancestor. Our Closable , written for classes inherited from Backbone.View is by no means an exception. In such a situation, the environment will not understand where the calls of the given ancestor methods occur in the mixin code, unless it explicitly specifies @extends :

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

On this, perhaps all, happy intervention!

English version in my blog
Backbone.Mix library
More code from the same authors: backbonex
What to do with jQuery noodles to bring it to mind when you can think of mixins? In english Immediately code
My twitter (only about the code)

Source: https://habr.com/ru/post/255865/


All Articles