2011.10.20

JavaScript chaining and Trainwreck.js


source images from public domain (1, 2) and edited by Miller Medeiros

Yesterday I released a piece of code that I wrote a long time ago, it’s called trainwreck.js and it’s main purpose is to provide easy method chaining.

The main reason behind it is because a lot of people don’t really understand how chaining works and end up extending jQuery or underscore.js just to be able to use chaining…

How does chaining work?

The concept is pretty simple and there is nothing magical about it. Let’s try to explain it with a little bit of code:

var foo = "1,2,3-a,b,c";

//anti-pattern! RegExp is the proper way of doing this kind of replacement.
var bar = foo
            .split(',')
            .join(' ')
            .split('-')
            .join(' ');

console.log(bar); // "1 2 3 a b c"

What just happened? String.prototype.split returns an Array and Array.prototype.join returns an String which means you can call a method on the returned value without assigning it to any variable, chaining works exactly like that! You are just calling a method on the object returned by the function.

// ORTHODOX CHAINING
var myObj = {

    _val : 'lorem',

    // "getter"/"setter"
    bar : function(val){
        if(typeof val === 'undefined'){
            return this._val;
        }else{
            this._val = val;
            return this; //chain
        }
    },

    // regular method
    foo : function(){
        console.log(this._val);
        return this; //chain
    }

};

//"chain, chain, chain..."
myObj.foo().bar('ipsum').foo();

Some people already know that I am not a huge fan of chaining and that I think it can make refactoring harder and code harder to understand when abused, but still a viable solution if used with care.

Trainwreck.js

Trainwreck.js creates an alias to an existing object providing a chainable API without needing to change the original code. It has drawbacks and also some benefits (like most things in life…).

//base object
var base = {
    val : 'lorem',
    trace : function(){
        console.log(this.val);
    }
};

//create a new object that wrap calls to the "base" object
var awsum = trainwreck.create(base);

//"chain, chain, chain.."
awsum.trace().val('ipsum').trace();

//note that `base` is modified, `foo` is just an alias.
console.log(base.val); //"ipsum"

Don’t abuse this technique just because it is easy to do so, Trainwreck.js is more a proof-of-concept than anything else, if you have control over the original code and think chaining would improve your API implement it natively, if not use trainwreck.js and “be happy”, just beware of the following side-effects:

Performance / Memory usage

Since trainwreck creates a new object, loops over all the properties and create new functions that wrap calls to the original method/property each time you call trainwreck.create() it will increase memory usage and also degrade performance since the number of function calls will double. – It shouldn’t be a huge concern in most cases since JS engines are getting faster each day, but I only recommend using it in static objects, if you are creating multiple instances of the same object it will need to be called on each instance and it will be an expensive operation.

Scope

The this keyword will always point to the base object, it is usually a good thing since you don’t need to worry about scope issues (even if code is running on a timeout / event handler) but you will lose the ability to change the execution context by using Function.prototype.apply and Function.prototype.call.

Conclusion

Remember what Uncle Ben once said to Peter Parker:

“With great power comes great responsibility”

Fork/Watch the project on github and please use github issues for feature requests and bug reports.

Stop modifying objects you don’t own without a good reason!


Comments

Leave a Comment

Please post only comments that will add value to the content of this page. Please read the about page to understand the objective of this blog and the way I think about it. Thanks.

Comments are parsed as Markdown and you can use basic HTML tags (a, blockquote, cite, code, del, em, strong) but some code may be striped if not escaped (specially PHP and HTML tags that aren't on the list). Line and paragraph breaks automatic. Code blocks should be indented with 4 spaces.