Hey, I'm writing a book too :) Click the link below to preorder and save ;)
»» Get the Only Reference You'll "ever" Need on JavaScript Engineering Interviews, Now! ««
May 3rd in o2.js Modules by .

JavaScript Method Kung-Fu

Kick-Ass JavaScript

Kick-Ass JavaScript

Functions are powerful construct in JavaScript. In this tutorial, we are going to do some advanced function manipulations with. Do you like Reflection? Then you’ll love this tutorial ;)


You can click here, to get the source code of this tutorial (12Kb zipped archive) »»
You can also visit the API documentation for o2.MethodHelper.

A Big Caveat!

Don’t use a pattern just for the sake of using it ;)

memento, partial, and curry, which we’ll se in this tutorial, are powerful JavaScript constructs.

What they do is similar to the Reflection operation in strong-typed languages like C#. That is to say, they change and augment the runtime behaviors of methods.

Just like Reflection, when used wisely, they are great helpers. When over-used, though, they’ll damage your code’s readability and maintainability.

Memoization

It’s possible that some of your JavaScript functions may be doing resource-intensive calculations that may take too long time to complete, or may consume too much of CPU power.

If constantly need to call these kinds of functions over and over again, it will be bad in terms of performance.

Luckily, there are several things you can do:

  • Leave the complex calculation burden to the Server, and call the Server’s corresponding web method, using AJAX and use the already-calculated output,
  • Cache the output for some time, and use the cached results,
  • If you know that you’ll need some of the results, pre-cache them before even using the function,
  • Or a mixture of both.

If you prefer a client-side, or server-side caching, you need to take into account when the cache will be out of date. Will it have an expiry time, so that when that interval is reached, the cache value is discarded and the function recalculates its output by doing its resource-extensive operation — or will the data always remain cached during the application’s life-cycle. — Though these are points to be mentioned in a further article.

For this section we’ll be observing a pattern called “memoization” only:

memoize: function() {
	var pad  = {};
	var args = Array.prototype.slice.call(arguments);
	var self = args.shift();
	var obj  = args.length > 0 ? args[0] : null;

	var memoizedFn = function() {
		// Copy the arguments object into an array:
		// this allows it to be used as a cache key.
		var args = [];
		for (var i = 0; i < arguments.length; i++) {
			args[i] = arguments[i];
		}

		// Evaluate the memoized function if it hasn't been evaluated with
		// these arguments before.
		if (!(args in pad)) {
			pad[args] = self.apply(obj, arguments);
		}

		return pad[args];
	};

	return memoizedFn;
},

The comments are self-explanatory.

Let’s see how the method behaves:

function resourceConsumingOperation(a,b){
	//just for the sake of the demo.
	return a+b;
}

var memoized = o2.MethodHelper.memoize(resourceConsumingOperation);

out ( memoized(3,2) );//returns 5, fresh calculation
out ( memoized(4,5) );//returns 9, fresh calculation
out ( memoized(3,2) );//returns 5, from cache

For repeated resource-extensive calculations, memoization pattern is a life-saver ;)

Do You Like Your Chicken With, or Without Curry?

Let’s look at another useful construct:

//See http://www.dustindiaz.com/javascript-curry/ for a discussion.
curry: function() {
	var args = [].slice.call(arguments);

	var context = args.shift();
	var fn = args.shift();

	return function() {
		return fn.apply(context, args.concat(Array.prototype.slice.call(arguments)));
	};

},

The curry function simply returns a closure, filling given arguments of the passed function, and remaining the rest to be applied later on.

This can be best demonstrated with an example:

function chicken(a, b, c){
	return 'To cook a "curry-sauced chicken" you need <b>' + a + '</b>, <b>' + b + 
	'</b>, and <b>' + c + '</b>.';
}
		
var curriedChicken = o2.MethodHelper.curry(this, chicken, 'a chicken', 'potato chips');
		
out( curriedChicken('curry sauce') );
//will output: To cook a "curry-sauced chicken" you need a chicken, potato chips, and curry sauce.

Neat, isn’t it?

Partial Methods

But, what if we want to prefill an arbitrary set of arguments, not the first few ones.

Then Partial Methods will be helpful:

// See http://ejohn.org/blog/partial-functions-in-javascript/ for a detailed discussion.
partial: function() {
	var args = Array.prototype.slice.call(arguments);

	var context = args.shift();
	var fn = args.shift();

	return function() {
		var arg = 0;

		for (var i = 0; i < args.length && arg < arguments.length; i++) {
			if (args[i] === undefined) {
				args[i] = arguments[arg++];
			}
		}

		return fn.apply(context, args);
	};

}

The code above acts just like curry. With one major difference: when you leave any argument undefined it’s marked as a placeholder.

Let’s see with an example:

var partialChicken = o2.MethodHelper.partial(this, chicken, undefined, 'pepper', undefined);
		
out( partialChicken('a mule', 'garlic'));
//will output: To cook a "curry-sauced chicken" you need a mule, pepper, and garlic.

Require All Arguments

Here’s another useful method:

me.requireAllArguments = function(fn) {
	return function() {
		// throw an error if the arguments' length do not match.
		if (arguments.length < fn.length) {
			throw format(config.constants.error.ARGUMENT_COUNT_MISMATCH, fn.length,
			arguments.length);
		}

		return fn.apply(this, arguments);
	};

};

And an example usage:

function lnx(x, lnx, c){
	return x * lnx - x + c;
}

var requireAll = o2.MethodHelper.requireAllArguments(lnx);

try {
	requireAll(42, 42);
} catch(e){
	out( e );//Expected 3 arguments but found 2
}
		
out( requireAll(42, 42, 42) );
//outputs: 1764

Method Overloading

What if we want to overload a method depending on the number of arguments it has.

o2.MethodHelper.overload is exactly meant for this:

me.overload = function(object, name, fn) {
	var old = object[name];

	object[name] = function() {
		// If both function have identical # of arguments,
		// then call the cached function.
		if(fn.length == arguments.length) {
			return fn.apply(this, arguments);
		}

		// Otherwise try to call the old function, if any.
		if(typeof old == 'function') {
			return old.apply(this, arguments);
		}
	};

};

And here’s a usage example:

var base = {};

o2.MethodHelper.overload(base, 'calculate', function(a){
	return a;
});

o2.MethodHelper.overload(base, 'calculate', function(a, b){
	return a * b;
});

o2.MethodHelper.overload(base, 'calculate', function(a, b, c){
	return Math.pow(c, a * b);
});		

out( base.calculate(2) );//2
out( base.calculate(2, 3) );//6
out( base.calculate(2, 3, 4) );//4096

Delayed Execution

Let’s assume we wish to delay the execution of a function for a certain amount of time. Then this code will help:

me.defer = function(fn, interval, context, args) {

	setTimeout( function() {
		return fn.apply(context,args);
	},interval);

};

And a usage example:

o2.MethodHelper.defer(function(){
	out('I will print after at least 2 seconds');
}, 2000);

That’s all for now. Hope you enjoyed it.
Do you have any magic tricks in your pocket?
Feel free to share them in your comments :)

It's me: Volkan! Jack of all Trades, Samurai of JavaScript ;) Since 2003, I’ve been doing front-end development on client-heavy AJAX web applications. Currently, I'm a JavaScript Hacker at "SocialWire". Before that I was a VP of Technology at "GROU.PS", a well-known do-it-yourself social networking platform that allows people to come together and form interactive communities around a shared interest or affiliation. Before that, I was a JavaScript Engineer at "LiveGO", a globally known social mash-up. Before that, I was the CTO of a business network ("cember.net") which was acquired by Xing AG for around 4.2M Euros. See my linkedin profile to find more about me; I also share worth-following bits an pieces on twitter.

VIsit Volkan Özçelik's website

Leave a Reply





o2.js _
Fork Ribbon