Better JavaScript Mixins - APPENDICES - Building Backbone Plugins: Eliminate The Boilerplate In Backbone.js Apps (2014)

Building Backbone Plugins: Eliminate The Boilerplate In Backbone.js Apps (2014)

PART 5: APPENDICES

Appendix D: Better JavaScript Mixins

warning

Unfinished Chapter

Note: This chapter is incomplete or contains known errors and inconsistencies. Further work is being done to finish this chapter. All feedback is welcome, still.

Mixins are generally easy in JavaScript, though they are semantically different than what Ruby calls a mixin, which are facilitated through inheritance behind the scenes. The most basic of mixins in JavaScript is done with an “extend” method from a library like underscore.js or jQuery. While this method of copying methods and attributes is powerful, it is also limited.

Object Extension Is A Poor Man’s Mixin

I love the “extend” methods of jQuery and Underscore. I use them a lot. They’re powerful and simple and make it easy to transfer data and behavior from one object to another – the essence of a mixin. But in spite of my love of underscore.js and jQuery’s “extend” methods, there’s a problem with them in that they apply every attribute from one or more source objects to a target objects.

You can see the effect of this in this example from Chris Missal:

1 var start = {

2 id: 123,

3 count: 41,

4 desc: 'this is information',

5 title: 'Base Object',

6 tag: 'uncategorized',

7 values: [1,1,2,3,5,8,13]

8 };

9 var more = {

10 name: 'Los Techies',

11 tag: 'javascript'

12 };

13 var extra = {

14 count: 42,

15 title: null,

16 desc: undefined,

17 values: [1,3,6,10]

18 };

19

20 var extended = _.extend(start, more, extra);

21 console.log(JSON.stringify(extended));

This produces the following output:

1 {

2 "id": 123,

3 "count": 42,

4 "title": null,

5 "tag": "javascript",

6 "values": [1,3,6,10],

7 "name": "Los Techies"

8 }

In this example, the first object contains a key named “values” and the third object also contains a key named “values”. When the extend method is called, the last one in wins. This means that the “values” from the third object end up being the values on the final target object.

So, what happens if we want to mix two behavioral sets in to one target object, but both of those behavior sets use the same underlying “values”, or “config”, or “bindings” (as reported in this Marionette issue)? One or both of them will break, and that’s definitely not a good thing.

A Problem Of Context

The good news is there’s an easy way to solve the mixin problem with JavaScript: only copy the methods you need from the behavior set in to the target.

That is, if the object you want to mix in to another has a method called “doSomething”, you shouldn’t be forced to copy the “config” and “_whatever” and “foo” and all the other methods and attributes off this object just to get access to the “doSomething” method. Instead, you should only have to copy the “doSomething” method from the behavior source to your target.

1 var foo = {

2 doSomething: function(){

3 // ...

4 }

5 }

6

7 var bar = {};

8 bar.doSomething = foo.doSomething;

But this poses it’s own challenge: the source of the behavior likely calls “this.config” and “this._whatever()” and other context-based methods and attributes to do it’s work. Simply copying the “doSomething” function from one object to another won’t work because the context of the function will change and the support methods / data won’t be found.

information

For more detail on context and how it changes, check out my JavaScript Context screencast.

Solving The Mixin Problem

To fix the mixin problem then, we need to do two things:

· Only copy the methods we need to the target

· Ensure the copied methods retain their original context when executing

This is easier than it sounds. We’ve already seen how requirement #1 can be solved, and requirement #2 can be handled in a number of ways, including the raw ECMAScript 5 “bind” method, the use of Underscore’s “bind” method, writing our own, or by using one of a number of other shims and plugins.

The way to facilitate a mixin from one object to another, then, looks something like this:

1 var foo = {

2 baz: function(){

3 // ...

4 },

5

6 config: [ ... ]

7 }

8

9 var bar = {

10 config: { ... }

11 };

12

13 // ECMAScript "bind"

14 bar.baz = foo.baz.bind(foo);

15

16 // Undescore "bind"

17 bar.baz = _.bind(foo.baz, foo);

18

19 // ... many more options

In this example, both the source object and the target object have a “config” attribute. Each object needs to use that config attribute to store and retrieve certain bits of data without clobbering each the other one, but I still want the “baz” function to be available directly on the “foo” object. To make this all work, I assign a bound version of the “baz” function to foo where the binding is set to to the bar object. That way whenever the baz function is called from foo, it will always run in the context of the original source – bar.

A Real Example

In this scenario, the model instance is extended with the functionality of the SelectableModel. This provides the select and deselect methods on the model directly, and still triggers the events from the model. Now the model instance can be used within the view with no oddities in accessing the selectable functionality.

1 SelectableView = BBPlug.ModelView.extend({

2

3 initialize: function(){

4 this.model.on("selected", this.modelSelected, this);

5 this.model.on("deselected", this.modelDeselected, this);

6 },

7

8 selectModel: function(){

9 this.model.select();

10 },

11

12 deselectModel: function(){

13 this.model.deselect();

14 }

15

16 });

17

18 var model = new MySelectableModel();

19 new SelectableView({

20 model: model

21 });

The downside to this approach is that the model is now being directly modified by the SelectabelModel in both the extension of the model, and when the select and deselect methods are called. This could have some potential side effects if care is not taken to ensure these methods and attributes are not overridden by other code. The benefit, though, is a more clear use of the selectable methods on the model. With a little planning and possibly some documentation or code comments around the model, the potential side effects can be avoided.

The end result, though, is that the SelectableModel is a very flexible object in terms of it’s use. It can be created on it’s own, with only a reference to a model. It can be mixed in to a model directly. Or it can be used in any of a number of other ways that JavaScript objects can be created, used and destroyed.

Ok, enough “foo, bar, baz” nonsense. Let’s look at a real example of where I’m doing this: Marionette’s use of Backbone.EventBinder. I want to bring the “bindTo”, “unbindFrom” and “unbindAll” methods from the EventBinder in to Marionette’s Application object, as one example. To do this while allowing the EventBinder to manage it’s own internal state and implementation details, I use the above technique of assigning the methods as bound functions:

1 Marionette.addEventBinder = function(target){

2

3 // create the "source" of the functionality i need

4 var eventBinder = new Marionette.EventBinder();

5

6 // add the methods i need to the target object, binding them correctly

7 target.bindTo = _.bind(eventBinder.bindTo, eventBinder);

8 target.unbindFrom = _.bind(eventBinder.unbindFrom, eventBinder);

9 target.unbindAll = _.bind(eventBinder.unbindAll, eventBinder);

10 };

11

12 // use the mixin method

13 var myApp = new Marionette.Application();

14 Marionette.addEventBinder(myApp);

Now when I call any of those three methods from my application instance, they still run in the context of my eventBinder object instance and they can access all of their internal state, configuration and behavior. But at the same time, I can worry less about whether or not the implementation details of the EventBinder are going to clobber the implementation details of the Application object. Since I’m being very explicit about which methods and attributes are brought over from the EventBinder, I can spend the small amount of cognitive energy that I need to determine whether or not the method I’m creating on the Application instance already exists. I don’t have to worry about the internal details like “_eventBindings” and other bits because they are not going to be copied over.

Simplifying Mixins

Given the repetition of creating mixins like this, it should be pretty easy to create a function that can handle the grunt work for you. All you need to supply is a target object, a source object and a list of methods to copy. The mixin function can handle the rest:

1 // build a mixin function to take a target that receives the mixin,

2 // a source that is the mixin, and a list of methods / attributes to

3 // copy over to the target

4

5 function mixInto(target, source, methodNames){

6

7 // ignore the actual args list and build from arguments so we can

8 // be sure to get all of the method names

9 var args = Array.prototype.slice.apply(arguments);

10 target = args.shift();

11 source = args.shift();

12 methodNames = args;

13

14 var method;

15 var length = methodNames.length;

16 for(var i = 0; i < length; i++){

17 method = methodNames[i];

18

19 // bind the function from the source and assign the

20 // bound function to the target

21 target[method] = _.bind(source[method], source);

22 }

23

24 }

25

26 // make use of the mixin function

27 var myApp = new Marionette.Application();

28 mixInto(myApp, Marionette.EventBinder, "bindTo", "unbindFrom", "unbindAll");

This should be functionally equivalent to the previous code that was manually binding and assigning the methods. But keep in mind that this code is not robust at all. Bad things will happen if you get the source’s method names wrong, for example. These little details should be handled in a more complete mixin function.

An Alternate Implementation: Closures

An alternate implementation for this can be facilitated without the use of a “bind” function. Instead, a simple closure can be set up around the source object, with a wrapper function that simply forwards calls to the source:

1 // build a mixin function to take a target that receives the mixin,

2 // a source that is the mixin, and a list of methods / attributes to

3 // copy over to the target

4

5 function mixInto(target, source, methodNames){

6

7 // ignore the actual args list and build from arguments so we can

8 // be sure to get all of the method names

9 var args = Array.prototype.slice.apply(arguments);

10 target = args.shift();

11 source = args.shift();

12 methodNames = args;

13

14 var method;

15 var length = methodNames.length;

16 for(var i = 0; i < length; i++){

17 method = methodNames[i];

18

19 // build a function with a closure around the source

20 // and forward the method call to the source, passing

21 // along the method parameters and setting the context

22 target[method] = function(){

23 var args = Array.prototype.slice(arguments);

24 source[method].apply(source, args);

25 }

26

27 }

28

29 }

30

31 // make use of the mixin function

32 var myApp = new Marionette.Application();

33 mixInto(myApp, Marionette.EventBinder, "bindTo", "unbindFrom", "unbindAll");

I’m not sure if this version is really “better” or not, but it would at least provide more backward compatibility support and fewer requirements. You wouldn’t need to patch ‘Function.prototype.bind’ or use a third party shim or library for older browsers. It should work with any browser that supports JavaScript, though I’m not sure how far back it would go. Chances are, though, that any browser people are still using would be able to handle this, including – dare I say it? – IE6 (maybe… I think… I’m not going to test that, though :P )

Potential Drawbacks

As great as all this looks and sounds, there are some limitations and drawbacks – and probably more than I’m even aware of right now.

We’re directly manipulating the context of the functions with this solution. While this has certainly provided a measured benefit, it can be dangerous. There may be (are likely) times that you just don’t want to mess with context – namely when you aren’t in control of the function context in the first place. Think about a jQuery function callback, for example. Typically, jQuery sets the context of a callback to the DOM element that was being manipulated and you might not want to mess with that.

In the case of my EventBinder with Marionette, I did run in to a small problem with the context binding. The original version of the code would default the context of callback functions to the object that “bindTo” was called from. This meant the callback for “myView.bindTo(…)” would be run with “myView” as the context. When I switched over to the above code that creates the bound functions, the default context changed. Instead of being the view, the context was set to the EventBinder instance itself, just like our code told it to. This had an effect on how my Marionette views were behaving and I had to work around that problem in another way.

There certainly some potential drawbacks to this, as noted. But if you understand that danger and you don’t try to abuse this for absolutely everything, I think this idea could work out pretty well as a way to produce a mixin system that really does favor composition over inheritance, and avoids the pitfalls of simple object extension.

Backbone.Include

There’s a library from Anthony Short that aims to solve the same problem for Backbone, specifically. Check out Backbone.Include for Anthony’s implementation.