IT IS HERE! Get Smashing Node.JS on Amazon Kindle today!
Show Posts
← Back to homepage

Given the Object-Oriented nature of the MooTools framework, code repetition is something that is long forgotten (or should be) in the scripts your write. With the avoidance of code repetition comes code reusability, which results in your website being easier to read, extend and maintain, and your scripts smaller in size.

At this point there’s no doubt in anyone’s mind that DRY is a principle we should stick to. However, let’s examine how to achieve this in the right way.

Natives extension

A script, no matter its purpose, deals with various native objects all the time. These may be native types, such as Strings, Arrays, Objects, Numbers, or objects that are not native to the language but are to the browser rendering engine, such as HTMLElement.

MooTools, as you already know, empowers us to extend these Natives really easily. To add a repeat() method to a String, this is all it takes:

String.implement({
	repeat: function(times){
		return new Array(times + 1).join(this);
	}
});

This is one of the pillars of code reusability within MooTools code. But, as I’ll explain later, it can be misused.

Self-invoking functions

Probably the most natural and straightforward way of avoiding code repetition is defining a function. The code above could have been written as:

function repeat(str, times){
	return new Array(times +1).join(str);
}

A downside of the snippet above is the pollution of the global namespace. The function repeat would be available to all your scripts within the window you execute it in, potentially colliding with other functions. Or making it harder to maintain when another programmer comes along and writes another repeat function, which may even do something different.

That’s when self-invoking functions come to the rescue. As you already know, the local variables defined within a function are only available to that context. If you decide to use functions instead of extending a native, this is the smart way to do it:

// we wrap our code in a self-invoking function
(function(){

   // local function within this context
   var repeat(str,times) = function(){
   	// ...
   });

   // notice we don't use var here, we want the Car class to be globally available
   Car = new Class({

      run: function() {
          var a = repeat('test', 20);
      }

   });

   ConvertibleCar = new Class({

      Extends: Car,

      display: function() {
          var b = repeat('some string', 20);
      }

   });

})();

// the repeat() we defined before is no longer accessible here

When to use which method

First of all, you might be wondering why you’d go through the trouble of defining a function within a sub-context if you could have extended String in the first place. That’s valid reasoning, but only because of the nature of the example.

If you’ve been writing MooTools code for a long time, maybe sometimes you found yourself polluting your own Natives, by extending them with functions that added little reusability value. An example:

Element.implement({

	doSomethingSoSpecificThatIevenFindItHardToName: function(){
        	// ...
	}

});

Sometimes extending the natives just doesn’t feel right, specially if it’s for a logic you repeat several times but only within a class or subset of classes, and it doesn’t award a public method within your class. That’s when a function wrapped in a self-invoking function described above is best suited.

To finalize, let’s see a real-world example that contrasts these two approaches, taken from the MooTools Core.

(function(){

var walk = function(element, walk, start, match, all, nocash){
	// internal dom walking logic
};

Element.implement({

	getPrevious: function(match, nocash){
		return walk(this, 'previousSibling', null, match, false, nocash);
	},

	getAllPrevious: function(match, nocash){
		return walk(this, 'previousSibling', null, match, true, nocash);
	},

	getNext: function(match, nocash){
		return walk(this, 'nextSibling', null, match, false, nocash);
	},

	getAllNext: function(match, nocash){
		return walk(this, 'nextSibling', null, match, true, nocash);
	},

	getFirst: function(match, nocash){
		return walk(this, 'nextSibling', 'firstChild', match, false, nocash);
	},

        // ...

});

})();

As you can see, it extends Element with methods that you reuse a lot, and that even MooTools utilizes in its other components. However, there’s no point in making the walk function available to the rest of the Core, let alone your scripts, since it’s used in a very specific part which deals with the DOM manipulation. Its only purpose is avoiding code repetition in the several methods it implements to Element.

22 Comments

aeron said

I noticed a lot of the Mootools core was re-written with these self-invoking functions in 1.2. Is the “self-invoking” part a label you came up with? They are similar to enclosures. In fact when I first saw it in 1.2 I thought it was a clever way of creating private functions with Javascript. Good post.

    Guillermo Rauch said

    I read it once or twice, I don’t think it’s a very popular term though. But it is indeed more clear than just saying ‘closure’, which is a broader concept.

EmEhRKay said

So we can move the clouds huh? Pretty cool.

Great post. I just recently extended a native (String.keyValToHash) and I love the simplicity.

How does using String.implement({}); Differ from prototyping directly to the string obj?

gonchuki said

@aeron:
that’s because they *are* closures. when I read “self invoking” I thought we was speaking about using arguments.callee() until I read the code.

Good article.

@EmEhRKay: using (String|Array|etc).implement doesnt override existing methods (for example the native Array.forEach and add’s the ‘generics’ to the Native, i.e. you could use String.repeat(“Moo”, 3); to get “MooMooMoo” after the snippet on the top of the article.

Also this closure works magic for “loops methods” (those you run for many dom elements), since you can define the function and call it once.

Good point with the article.

What about _”immediately executed anonymous function”_ ?

Chris said

Great article!

Your thoughts?

About Guillermo Rauch:

CTO and co-founder of LearnBoost / Cloudup (acquired by Automattic in 2013). Argentine living in SF.