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 Chris’ underscore example:
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
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.
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.
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:
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
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:
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.
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:
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:
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.
Post Footer automatically generated by Add Post Footer Plugin for wordpress.