O'Reilly logo

Beautiful JavaScript by Anton Kovalyov

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

CHAPTER ONE

Beautiful Mixins

Developers love to create overly complex solutions to things that aren’t really problems.

Thomas Fuchs

In the beginning there was code, and the code was verbose, so we invented functions that the code might be reused. But after a while there were also too many functions, so we looked for a way to reuse those too. Developers often go to great lengths to apply “proper” reuse techniques to JavaScript. But sometimes when we try too hard to do the right thing, we miss the beautiful thing right in front of our eyes.

Classical Inheritance

Many developers schooled in Java, C++, Objective-C, and Smalltalk arrive at JavaScript with an almost religious belief in the necessity of the class hierarchy as an organizational tool. Yet humans are not good at classification. Working backward from an abstract superclass toward real types and behaviors is unnatural and restrictive—a superclass must be created before it can be extended, yet classes closer to the root are by nature more generic and abstract and are more easily defined after we have more knowledge of their concrete subclasses. Moreover, the need to tightly couple types a priori such that one type is always defined solely in terms of another tends to lead to an overly rigid, brittle, and often ludicrous model (“Is a button a rectangle or is it a control? Tell you what, let’s make Button inherit from Rectangle, and Rectangle can inherit from Control…no, wait a minute…”). If we don’t get it right early on, our system is forever burdened with a flawed set of relationships—and on those rare occasions that, by chance or genius, we do get it right, anything but a minimal tree structure usually represents too complex a mental model for us to readily visualize.

Classical inheritance is appropriate for modeling existing, well-understood hierarchies—it’s okay to unequivocally declare that a FileStream is a type of InputStream. But if the primary motivation is function reuse (and it usually is), classical hierarchies can quickly become gnarly labyrinths of meaningless subtypes, frustrating redundancies, and unmanageable logic.

Prototypes

It’s questionable whether the majority of behaviors can ever be mapped to objectively “right” classifications. And indeed, the classical inheritance lobby is countered by an equally fervent band of JavaScript loyalists who proclaim that JavaScript is a prototypal, not classical, language and is deeply unsuited to any approach that includes the word class. But what does “prototypal” mean, and how do prototypes differ from classes?

In generic programming terms, a prototype is an object that supplies base behavior to a second object. The second object can then extend this base behavior to form its own specialization. This process, also known as differential inheritance, differs from classical inheritance in that it doesn’t require explicit typing (static or dynamic) or attempt to formally define one type in terms of another. While classical inheritance is planned reuse, true prototypal inheritance is opportunistic.

In general, when working with prototypes, one typically chooses not to categorize but to exploit alikeness.

Antero Taivalsaari, Nokia Research Center

In JavaScript, every object references a prototype object from which it can inherit properties. JavaScript prototypes are great instruments for reuse: a single prototype instance can define properties for an infinite number of dependent instances. Prototypes may also inherit from other prototypes, thus forming prototype chains.

So far, so good. But, with a view to emulating Java, JavaScript tied the prototype property to the constructor. As a consequence, more often than not, multilevel object inheritance is achieved by chaining constructor-prototype couplets. The standard implementation of a JavaScript prototype chain is too grisly to appear in a book about beautiful JavaScript, but suffice it to say, creating a new instance of a base prototype in order to define the initial properties of its inheritor is neither graceful nor intuitive. The alternative—manually copying properties between prototypes and then meddling with the constructor property to fake real prototypal inheritance—is even less becoming.

Syntactic awkwardness aside, constructor-prototype chaining requires upfront planning and results in structures that more closely resemble the traditional hierarchies of classical languages than a true prototypal relationship: constructors represent types (classes), each type is defined as a subtype of one (and only one) supertype, and all properties are inherited via this type chain. The ES6 class keyword merely formalizes the existing semantics. Leaving aside the gnarly and distinctly unbeautiful syntax characteristic in constructor-prototype chains, traditional JavaScript is clearly less prototypal than some would claim.

In an attempt to support less rigid, more opportunistic prototypes, the ES5 specification introduced Object.create. This method allows a prototype to be assigned to an object directly and therefore liberates JavaScript prototypes from constructors (and thus categorization) so that, in theory, an object can acquire behavior from any other arbitrary object and be free from the constraints of typecasting:

var circle = Object.create({
  area: function() {
    return Math.PI * this.radius * this.radius;
  },
  grow: function() {
    this.radius++;
  },
  shrink: function() {
    this.radius--;
  }
});

Object.create accepts an optional second argument representing the object to be extended. Sadly, instead of accepting the object itself (in the form of a literal, variable, or argument), the method expects a full-blown meta property definition:

var circle = Object.create({
  /*see above*/
}, {
  radius: {
    writable:true, configurable:true, value: 7
  }
});

Assuming no one actually uses these unwieldy beasts in real code, all that remains is to manually assign properties to the instance after it has been created. Even then, the Object.create syntax still only enables an object to inherit the properties of a single prototype. In real scenarios, we often want to acquire behavior from multiple prototype objects: for example, a person can be an employee and a manager.

Mixins

Fortunately, JavaScript offers viable alternatives to inheritance chaining. In contrast to objects in more rigidly structured languages, JavaScript objects can invoke any function property regardless of lineage. In other words, JavaScript functions don’t need to be inheritable to be visible—and with that simple observation, the entire justification for inheritance hierarchies collapses like a house of cards.

The most basic approach to function reuse is manual delegation—any public function can be invoked directly via call or apply. It’s a powerful and easily overlooked feature. However, aside from the verbosity of serial call or apply directives, such delegation is so convenient that, paradoxically, it sometimes actually works against structural discipline in your code—the invocation process is sufficiently ad hoc that in theory there is no need for developers to organize their code at all.

Mixins are a good compromise: by encouraging the organization of functionality along thematic lines they offer something of the descriptive prowess of the class hierarchy, yet they are light and flexible enough to avoid the premature organization traps (and head-spinning dizziness) associated with deeply chained, single-ancestry models. Better still, mixins require minimal syntax and play very well with unchained JavaScript prototypes.

The Basics

Traditionally, a mixin is a class that defines a set of functions that would otherwise be defined by a concrete entity (a person, a circle, an observer). However, mixin classes are considered abstract in that they will not themselves be instantiated—instead, their functions are copied (or borrowed) by concrete classes as a means of inheriting behavior without entering into a formal relationship with the behavior provider.

Okay, but this is JavaScript, and we have no classes per se. This is actually a good thing because it means we can use objects (instances) instead, which offer clarity and flexibility: our mixin can be a regular object, a prototype, a function, whatever, and the mixin process becomes transparent and obvious.

The Use Case

I’m going to discuss a number of mixin techniques, but all the coding examples are directed toward one use case: creating circular, oval, or rectangular buttons (something that would not be readily possible using conventional classical inheritance techniques). Here’s a schematic representation: square boxes represent mixin objects, and rounded boxes represent the actual buttons.

Button reuse plan

Classic Mixins

Scanning the first two pages returned from a Google search for “javascript mixin,” I noticed the majority of authors define the mixin object as a full-blown constructor type with its function set defined in the prototype. This could be seen as a natural progression—early mixins were classes, and this is the closest thing JavaScript has to a class. Here’s a circle mixin modeled after that style:

var Circle = function() {};
Circle.prototype = {
  area: function() {
    return Math.PI * this.radius * this.radius;
  },
  grow: function() {
    this.radius++;
  },
  shrink: function() {
    this.radius--;
  }
};

In practice, however, such a heavyweight mixin is unnecessary. A simple object literal will suffice:

var circleFns = {
  area: function() {
    return Math.PI * this.radius * this.radius;
  },
  grow: function() {
    this.radius++;
  },
  shrink: function() {
    this.radius--;
  }
};

Here’s another mixin defining button behavior (for the sake of demonstration, I’ve substituted a simple log call for the working implementation of some function properties):

var clickableFns = {
  hover: function() {
    console.log('hovering');
  },
  press: function() {
    console.log('button pressed');
  },
  release: function() {
    console.log('button released');
  },
  fire: function() {
    this.action.fire();
  }
};

The extend Function

How does a mixin object get mixed into your object? By means of an extend function (sometimes known as augmentation). Usually extend simply copies (not clones) the mixin’s functions into the receiving object. A quick survey reveals some minor variations in this implementation. For example, the Prototype.js framework omits a hasOwnProperty check (suggesting the mixin is not expected to have enumerable properties in its prototype chain), while other versions assume you want to copy only the mixin’s prototype object. Here’s a version that is both safe and flexible:

function extend(destination, source) {
  for (var key in source) {
    if (source.hasOwnProperty(key)) {
      destination[key] = source[key];
    }
  }
  return destination;
}

Now let’s extend a base prototype with the two mixins we created earlier to make a RoundButton.prototype:

var RoundButton = function(radius, label) {
  this.radius = radius;
  this.label = label;
};

extend(RoundButton.prototype, circleFns);
extend(RoundButton.prototype, clickableFns);

var roundButton = new RoundButton(3, 'send');
roundButton.grow();
roundButton.fire();

Functional Mixins

If the functions defined by mixins are intended solely for the use of other objects, why bother creating mixins as regular objects at all? Isn’t it more intuitive to think of mixins as processes instead of objects? Here are the circle and button mixins rewritten as functions. We use the context (this) to represent the mixin’s target object:

var withCircle = function() {
  this.area = function() {
    return Math.PI * this.radius * this.radius;
  };
  this.grow = function() {
    this.radius++;
  };
  this.shrink = function() {
    this.radius--;
  };
};

var withClickable = function() {
  this.hover = function() {
    console.log('hovering');
  };
  this.press = function() {
    console.log('button pressed');
  };
  this.release = function() {
    console.log('button released');
  };
  this.fire = function() {
    this.action.fire();
  };
}

And here’s our RoundButton constructor. We’ll want to apply the mixins to RoundButton.prototype:

var RoundButton = function(radius, label, action) {
    this.radius = radius;
    this.label = label;
    this.action = action;
};

Now the target object can simply inject itself into the functional mixin by means of Function.prototype.call, cutting out the middleman (the extend function) entirely:

withCircle.call(RoundButton.prototype);
withClickable.call(RoundButton.prototype);

var button1 = new RoundButton(4, 'yes!', function() {return 'you said yes!'});
button1.fire(); //'you said yes!'

This approach feels right. Mixins as verbs instead of nouns; lightweight one-stop function shops. There are other things to like here too. The programming style is natural and concise: this always refers to the receiver of the function set instead of an abstract object we don’t need and will never use; moreover, in contrast to the traditional approach, we don’t have to protect against inadvertent copying of inherited properties, and (for what it’s worth) functions are now cloned instead of copied.

Adding Options

This functional strategy also allows mixed in behaviors to be parameterized by means of an options argument. The following example creates a withOval mixin with a custom grow and shrink factor:

var withOval = function(options) {
  this.area = function() {
    return Math.PI * this.longRadius * this.shortRadius;
  };
  this.ratio = function() {
    return this.longRadius/this.shortRadius;
  };
  this.grow = function() {
    this.shortRadius += (options.growBy/this.ratio());
    this.longRadius += options.growBy;
  };
  this.shrink = function() {
    this.shortRadius -= (options.shrinkBy/this.ratio());
    this.longRadius -= options.shrinkBy;
  };
}

var OvalButton = function(longRadius, shortRadius, label, action) {
  this.longRadius = longRadius;
  this.shortRadius = shortRadius;
  this.label = label;
  this.action = action;
};

withButton.call(OvalButton.prototype);
withOval.call(OvalButton.prototype, {growBy: 2, shrinkBy: 2});

var button2 = new OvalButton(3, 2, 'send', function() {return 'message sent'});
button2.area(); //18.84955592153876
button2.grow();
button2.area(); //52.35987755982988
button2.fire(); //'message sent'

Adding Caching

You might be concerned that this approach creates additional performance overhead because we’re redefining the same functions on every call. Bear in mind, however, that when we’re applying functional mixins to prototypes, the work only needs to be done once: during the definition of the constructors. The work required for instance creation is unaffected by the mixin process, since all the behavior is preassigned to the shared prototype. This is how we support all function sharing on the twitter.com site, and it produces no noticeable latency. Moreover, it’s worth noting that performing a classical mixin requires property getting as well as setting, and in fact functional mixins appear to benchmark quicker in the Chrome browser than traditional ones (although this is obviously subject to considerable variance).

That said, it is possible to optimize functional mixins further. By forming a closure around the mixins we can cache the results of the initial definition run, and the performance improvement is impressive. Functional mixins now easily outperform classic mixins in every browser.

Here’s a version of the withRectangle mixin with added caching:

var withRectangle = (function() {
  function area() {
    return this.length * this.width;
  }
  function grow() {
    this.length++, this.width++;
  }
  function shrink() {
    this.length--, this.width--;
  }
  return function() {
    this.area = area;
    this.grow = grow;
    this.shrink = shrink;
    return this;
  };
})();

var RectangularButton = function(length, width, label, action) {
  this.length = length;
  this.width = width;
  this.label = label;
  this.action = action;
}

withClickable.call(RectangularButton.prototype);
withRectangle.call(RectangularButton.prototype);

var button3 =
  new RectangularButton(4, 2, 'delete', function() {return 'deleted'});
button3.area(); //8
button3.grow();
button3.area(); //15
button3.fire(); //'deleted'

Advice

One danger with any kind of mixin technique is that a mixin function will accidentally overwrite a property of the target object that, coincidentally, has the same name. Twitter’s Flight framework, which makes use of functional mixins, guards against clobbering by temporarily locking existing properties (using the writable meta property) during the mixin process.

Sometimes, however, instead of generating a collision error we might want the mixin to augment the corresponding method on the target object. advice redefines a function by adding custom code before, after, or around the original implementation. The Underscore framework implements a basic function wrapper that enables advice:

button.press = function() {
  mylib.appendClass('pressed');
};

//after pressing button, reduce shadow (using underscore)
button.pressWithShadow = _.wrap(button.press, function(fn) {
  fn();
  button.reduceShadow();
}

The Flight framework takes this a stage further: now the advice object is itself a functional mixin that can be mixed into target objects to enable advice for subsequent mixins.

Let’s use this advice mixin to augment our rectangular button actions with shadow behavior. First we apply the advice mixin, followed by the two mixins we used earlier:

withAdvice.call(RectangularButton.prototype);
withClickable.call(RectangularButton.prototype);
withRectangle.call(RectangularButton.prototype);

And now the withShadow mixin that will take advantage of the advice mixin:

var withShadow = function() {
  this.after('press', function() {
    console.log('shadow reduced');
  };
  this.after('release', function() {
    console.log('shadow reset');
  };
};

withShadow.call(RectangularButton.prototype);
var button4 = new RectangularButton(5, 4);
button4.press(); //'button pressed' 'shadow reduced'
button4.release(); //'button released' 'shadow reset'

The Flight framework sugarcoats this process. All flight components get withAdvice mixed in for free, and there’s also a defineComponent method that accepts multiple mixins at a time. So, if we were using Flight we could further simplify the process (in Flight, constructor properties such as rectangle dimensions are defined as attr properties in the mixins):

var RectangularButton =
  defineComponent(withClickable, withRectangle, withShadow);
var button5 = new RectangularButton(3, 2);
button5.press(); //'button pressed' 'shadow reduced'
button5.release(); //'button released' 'shadow reset'

With advice we can define functions on mixins without having to guess whether they’re also implemented on the target object, so the mixin can be defined in isolation (perhaps by another vendor). Conversely, advice allows us to augment third-party library functions without resorting to monkey patching.

Wrapup

When possible, cut with the grain. The grain tells you which direction the wood wants to be cut. If you cut against the grain, you’re just making more work for yourself, and making it more likely you’ll spoil the cut.

Charles Miller1

As programmers, we’re encouraged to believe that certain techniques are indispensable. Ever since the early 1990s, object-oriented programming has been hot, and classical inheritance has been its poster child. It’s not hard to see how a developer eager to master a new language would feel under considerable pressure to fit classical inheritance under the hood.

But peer pressure is not an agent of beautiful code, and neither is serpentine logic. When you find yourself writing Circle.prototype.constructor = Circle, ask yourself if the pattern is serving you, or you’re serving the pattern. The best patterns tread lightly on your process and don’t interfere with your ability to use the full power of the language.

By repeatedly defining an object solely in terms of another, classical inheritance establishes a series of tight couplings that glue the hierarchy together in an orgy of mutual dependency. Mixins, in contrast, are extremely agile and make very few organizational demands on your codebase—mixins can be created at will, whenever a cluster of common, shareable behavior is identified, and all objects can access a mixin’s functionality regardless of their role within the overall model. Mixin relationships are entirely ad hoc: any combination of mixins can be applied to any object, and objects can have any number of mixins applied to them. Here, at last, is the opportunistic reuse that prototypal inheritance promised us.

1 See Charles Miller’s entire post at his blog, The Fishbowl.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required