How to use mixins properly in Javascript
Asked Answered
L

4

8

I am organizing a small enterprise application but would like to be as DRY as possible. As a result, I've been looking at mixin libraries.

I came across this library and thought it might be a good option as it allows you to mix in and out at run time. Also, I can just have one base class (BaseView) for instance and just mixin off of that.

Questions

  1. What are some real application examples of useful Mixins? (pls no more abstract examples)
  2. Do I even need to extend classes or can I just use this library to manage all extensions and mixins?
Lebensraum answered 29/11, 2012 at 18:28 Comment(0)
S
4

please see also:

If it comes to JavaScript and Role based composition approaches like Mixins and Traits, I'm meanwhile very opinionated. I always will point to a library agnostic mix of 2 purely function based patterns - firstly the module pattern and secondly the "Flight Mixin" pattern as it has been rediscovered, named and described by Angus Croll in May 2011. But I also would recommend reading a paper of mine from April 2014.

Questions

  • 1) What are some real application examples of useful Mixins? (pls no more abstract examples)
  • 2) Do I even need to extend classes or can I just use this library to manage all extensions and mixins?

Answering your 2 questions ...

1st) [Observable] probably is one of the most common real world examples for Mixins. But this is not the right place for providing its entire code base. The successively growing examples from the Smart Talents chapter do provide working implementations of a [Queue] factory that in the beginning just uses different Mixins like [Enumerable] and [Allocable] but finally also applies the already mentioned [Observable].

2nd) Just make use of a module system of your choice or need - CommonJS or AMD. Your factory modules or even instances/objects then do retrieve additional behavior by delegation; thus they actively do call / apply the Mixin or Trait modules.

finally - shortened example code:

var Observable_SignalsAndSlots = (function () {

  var
    Event = function (target, type) {

      this.target = target;
      this.type = type;
    },

    EventListener = function (target, type, handler) {

      var defaultEvent = new Event(target, type);

      this.handleEvent = function (evt) {
        /* ... */
      };
      this.getType = function () {
        return type;
      };
      this.getHandler = function () {
        return handler;
      };
    },

    EventTargetMixin = function () {

      var eventMap = {};

      this.addEventListener = function (type, handler) {
        /* ... */
      };
      this.dispatchEvent = function (evt) {
        /* ... */
      };
    }
  ;

  return EventTargetMixin;

}).call(null);


var Queue = (function () {

  var
    global = this,

    Observable  = global.Observable_SignalsAndSlots,
  //Allocable   = global.Allocable,

    Queue,

    onEnqueue = function (queue, type) {
      queue.dispatchEvent({type: "enqueue", item: type});
    },
    onDequeue = function (queue, type) {
      queue.dispatchEvent({type: "dequeue", item: type});
    },
    onEmpty = function (queue) {
      queue.dispatchEvent("empty");
    }
  ;

  Queue = function () { // implementing the [Queue] Constructor.
    var
      queue = this,
      list = []
    ;
    queue.enqueue = function (type) {

      list.push(type);
      onEnqueue(queue, type);

      return type;
    };
    queue.dequeue = function () {

      var type = list.shift();
      onDequeue(queue, type);

      (list.length || onEmpty(queue));

      return type;
    };
    Observable.call(queue);
  //Allocable.call(queue, list);
  };

  return Queue;

}).call(null);


var q = new Queue;

q.addEventListener("enqueue", function (evt) {console.log("enqueue", evt);});
q.addEventListener("dequeue", function (evt) {console.log("dequeue", evt);});
q.addEventListener("empty", function (evt) {console.log("empty", evt);});

console.log("q.addEventListener : ", q.addEventListener);
console.log("q.dispatchEvent : ", q.dispatchEvent);

console.log("q.enqueue('the') ... ", q.enqueue('the'));     // "enqueue" Object {type: "enqueue", item: "the", target: Queue}
console.log("q.enqueue('quick') ... ", q.enqueue('quick')); // "enqueue" Object {type: "enqueue", item: "quick", target: Queue}
console.log("q.enqueue('brown') ... ", q.enqueue('brown')); // "enqueue" Object {type: "enqueue", item: "brown", target: Queue}
console.log("q.enqueue('fox') ... ", q.enqueue('fox'));     // "enqueue" Object {type: "enqueue", item: "fox", target: Queue}

console.log("q.dequeue() ... ", q.dequeue()); // "dequeue" Object {type: "dequeue", item: "the", target: Queue}
console.log("q.dequeue() ... ", q.dequeue()); // "dequeue" Object {type: "dequeue", item: "quick", target: Queue}
console.log("q.dequeue() ... ", q.dequeue()); // "dequeue" Object {type: "dequeue", item: "brown", target: Queue}
console.log("q.dequeue() ... ", q.dequeue()); // "dequeue" Object {type: "dequeue", item: "fox", target: Queue}
                                              // "empty"   Object {target: Queue, type: "empty"}
console.log("q.dequeue() ... ", q.dequeue()); // "dequeue" Object {type: "dequeue", item: undefined, target: Queue}
                                              // "empty"   Object {target: Queue, type: "empty"}
.as-console-wrapper { max-height: 100%!important; top: 0; }
Shantung answered 29/11, 2012 at 18:28 Comment(0)
P
3

A mixin is just a different conceptual idea, of how to organize code and inheritance. You can of course combine it with using classical or prototypal inheritance, but it also works stand-alone, so to speak.

For instance, instead of creating "delegated" object properties/lookups (like prototypal inheritance), we would truly "form" new stand-alone objects, from multiple other objects. This is also called "multiple inheritance" sometimes and that cannot get achieved easily with Javascripts prototypal inheritance alone.

As an example:

var pianist = {
   play: function() {}
};

var programmner: {
   code: function() {}
};

And now we could create another Object, like

var Jim = Object.create( null ); // create a fully self-defining object

extend( Jim, pianist );
extend( Jim, programmer );

and this pseudo extend method could look like (ES5):

function extend( target, source ) {
    Object.getOwnPropertyNames( source ).forEach(function( key ) {
        Object.defineProperty( target, key, Object.getOwnPropertyDescriptor(source, key)) });

    return target
}

I actually didn't answer your questions properly, but I felt like there is no real answer to your question. It is as real as you are going to use it, there is no "application specific" use case really.

Pudency answered 29/11, 2012 at 18:35 Comment(0)
S
1

We use a mixin library called Cocktail (mixins...get it?). It's specifically for use in Backbone apps, but is quite good.

We've written up details on our usage patterns which do a better job than I can here describing it.

Spidery answered 1/7, 2013 at 18:11 Comment(0)
B
1

In Object-Oriented Design Patterns, the goal is to eliminate inheritance as much as possible, for inheritance is inflexible and jumbles together unrelated entities. You should program to an interface, not an implementation. Separate what varies from what stays the same. Design Patterns introduces the concept of delegation in the form of the strategy pattern. But due to JavaScript's dynamic nature, we can also easily use the concept of mixins to separate what varies from what stays the same.

Another reason to opt for mixins is that since JavaScript is a prototypal language, it chains objects together via the prototype chain, but if you overuse this feature, you will discover that chaining prototypes is slow in huge chains.

In fact, by using mixins, you can add functionality directly to the prototype of an object rather than chaining prototypes, reducing the expense of the lookup algorithm. So, for example, below we create two singleton objects 'speak' and 'fly'. These are objects that implement a behavior. Different things can speak and fly, but those different things should not inherit from each other. For example, you might have a Mallard Duck and a Parrot.

var speakable = {
    speak: function(){ return this.name + " speaks" }
}

var flyable = {
    fly: function(){ return this.name + " flies" }
}

In the jQuery world and previous to ES6's clean class syntax, you would add the mixins as follows utilizing constructor functions:

var MallardDuck = function(name){
  this.name = name;
}

var Parrot = function(name){
  this.name = name;
}

$.extend(MallardDuck.prototype, speakable, flyable);
$.extend(Parrot.prototype, speakable, flyable);

var duck = new MallardDuck('Bob');
var parrot = new Parrot('Jane');
console.log(duck.speak());
console.log(parrot.speak());

Without jQuery and again prior to ES6, if you wanted to extend an object with functionality via a mixin facility, then you could simply write your own extend mixin facility:

function extend(target){
  // if no mixin objects are provided, then return out of function
  if(!arguments[1])
    return;

  // the first argument is the target object we want to mix behavior into, so we start our loop at index 1
  for(var i = 1; j = arguments.length; i < j; i++) {
    // grab the singleton object to mixin into target
    var source = arguments[i];

    for(var propr in source) {
      // ensure we do not override a property that target already has
      if(!target[prop] && source.hasOwnProperty(prop)){
        target[prop] = source[prop];
      }
    }
  }
}

Rather than relying on an external library like jQuery, which is strongly discouraged for this simple purpose, or writing our own utility mixin facility, we can utilize the Object.assign property available in ES6. Furthermore, we can utilize this within ES6 classes. The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object.

const speakable = {
    speak: function(){ return `${this.name} speaks`; }
}

const flyable = {
    fly: function(){ return `${this.name} flies` }
}


class MallardDuck {
  constructor(name) {
    this.name = name;
    Object.assign(MallardDuck.prototype, speakable); // add the mixin to prototype chain. Alternatively, use 'this' to add directly to the new object being created
  }
}

class Parrot {
  constructor(name) {
    this.name = name;
    Object.assign(Parrot.prototype, speakable); // add the mixin to prototype chain. Alternatively, use 'this' to add directly to the new object being created
  }
}

const duck = new MallardDuck('Mallard Duck');
const parrot = new Parrot('Parrot');
console.log(duck.speak());
console.log(parrot.speak());
Bernat answered 23/6, 2019 at 15:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.