Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

An Example Of Overloaded Functions With Very Different Sub-Function Implementations

By Ben Nadel on

A few days ago, I blogged about overloading Javascript functions with a sub-function approach. In that post, I demonstrated that branching logic could be factored-out into the core function in such a way that the individual sub-functions could execute with a very narrow set of concerns. Such a separation might seem overkill when the difference in arguments is not terribly influential. But, sometimes, a difference in invocation arguments can lead to a completely different execution. I wanted to take a quick moment to consider such a scenario in order to look at why separating branching logic is important.

I recently demonstrated how to use jQuery's animate() method to power easing-based iteration. Now, I want to take that concept and extend it to use argument-based branching logic in which the "duration" argument of the ease() method becomes optional. In this overloaded method signature, the duration-based easing will use the animate() implementation (as it did before) while the non-duration-based iteration will use a standard FOR-loop.

<!DOCTYPE html>
<html>
<head>
	<title>Overloading The Ease Method Based On Arguments</title>
	<script type="text/javascript" src="./jquery-1.4.2.js"></script>
	<script type="text/javascript">

		// I am the easing iteration funciton. This is built on top
		// of the core animate function so that it can leverage the
		// built-in timer optimization.
		jQuery.ease = function( start, end, duration, easing, callback ){

			// Check to see how many arguments we have. If we only
			// have four, then we are using standard iteration. If
			// we have five, we are using duration-based iteration.
			if (arguments.length == 4){

				// Standard iteration.
				return(
					jQuery.ease.iterate.apply( this, arguments )
				);

			} else {

				// Duration-based iteration.
				return(
					jQuery.ease.iterateWithDuration.apply( this, arguments )
				);

			}
		};


		// I am the standard iteration method for easing.
		// Essentially, I am just a FOR-LOOP with a given
		// callback to execute per iteration.
		jQuery.ease.iterate = function(
			start,
			end,
			easing,
			callback
			){

			// Keep track of the iterations.
			var stepIndex = 0;

			// Use the difference between the start and end values
			// in order to mimic duration. We are going to assume
			// that the the FOR-Loop we are using has a +=1 nature.
			var duration = Math.max(
				Math.abs( end - start ),
				1
			);

			// Keep looping until the iteration index equals the
			// end index.
			for (var i = start ; i != end ; true ){

				// Execute the callback for this iteration.
				callback(
					i,
					stepIndex++,
					start,
					end
				);

				// Update the index value for our easing.
				i = jQuery.easing[ easing ](
					(stepIndex / duration),
					stepIndex,
					start,
					(end - start),
					duration
				);

			}

			// Execute the callback once for end value (which would
			// not have executed in the FOR-LOOP).
			callback( i, stepIndex, start, end );
		};


		// I am the duration-based iteration method for easing.
		// I use jQuery's animate() method to power the easing
		// over the given time frame.
		jQuery.ease.iterateWithDuration = function(
			start,
			end,
			duration,
			easing,
			callback
			){

			// Create a jQuery collection containing the one element
			// that we will be animating internally.
			var easer = $( "<div>" );

			// Keep track of the iterations.
			var stepIndex = 0;

			// Set the start index of the easer.
			easer.css( "easingIndex", start );

			// Animate the easing index to the final value. For each
			// step of the animation, we are going to pass the
			// current step value off to the callback.
			easer.animate(
				{
					easingIndex: end
				},
				{
					easing: easing,
					duration: duration,
					step: function( index ){
						// Invoke the callback for each step.
						callback(
							index,
							stepIndex++,
							start,
							end
						);
					}
				}
			);
		};


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// When the DOM is ready, init the scripts.
		$(function(){

			// Get a reference to the body tag.
			var body = $( "body" );

			// Get a reference to both lists.
			var durationList = $( "#durationList" );
			var iterateList = $( "#iterateList" );


			// Iterate withOUT druation.
			//
			// NOTE: We are putting the non-duration iteration first
			// because it is processor intensive and will mess up
			// the animation-based duration (making it less smooth).
			$.ease(
				1,
				100,
				"swing",
				function( i ){
					// Create the list element for this iteration.
					var listItem = $(
						"<li />",
						{
							text: i,
							css: {
								backgroundColor: "#CC0000",
								color: "#FFFFFF",
								width: ((i * 10) + "px")
							}
						}
					);

					// Add the list item to the list.
					iterateList.append( listItem );
				}
			);


			// Iterate with druation.
			$.ease(
				1,
				1000,
				1300,
				"swing",
				function( i ){
					// Create the list element for this iteration.
					var listItem = $(
						"<li />",
						{
							text: i,
							css: {
								backgroundColor: "#CC0000",
								color: "#FFFFFF",
								width: (i + "px")
							}
						}
					);

					// Add the list item to the list.
					durationList.append( listItem );
				}
			);

		});

	</script>
</head>
<body>

	<h1>
		Overloading The Ease Method Based On Arguments
	</h1>


	<h2>
		Easing With Duration (Animation)
	</h2>

	<ol id="durationList">
		<!-- To be populated via easing. -->
	</ol>


	<h2>
		Easing Without Duration
	</h2>

	<ol id="iterateList">
		<!-- To be populated via easing. -->
	</ol>

</body>
</html>

As you can see in the above code, I am using both forms of easing-based iteration to build an Ordered List (OL). Each list item in the ordered list is set to a width that is proportional to the index of the overall iteration. When we run this code, we get the following page output:

Duration And Non-Duration Based Easing Using jQuery's Animate() Method And FOR-Loops.

The difference between these two approaches (duration and non-duration) is rather substantial; one executes over a given time period (duration) whereas the other executes immediately. This behavioral difference requires a completely different implementation behind the two different invocations. Because of the difference of implementation, it is critical that the branching logic be factored-out of the primary execution function. If it were not, the primary execution function - ease() - would become large and unmaintainable.

I'm sure it's easy to look at this example and argue that these two different implementations shouldn't be two branches of a single method - that they should actually be two entirely different methods (ie. ease() and easeOverDuration()). That might be true; but, this post isn't really about easing - it's a case-in-point of how factoring-out branching logic based on arguments creates clean, cohesive functions with narrow execution concerns.



Reader Comments

Why not wrap it:
Function = function(){
}
Function.prototype = {
implementations: []
, call: function () {
if (typeof this.implementations[arguments.length] == "function")
{
this.implementations[arguments.length].apply ( null, arguments );
}
}
}

And then it could be set up with:
easing = new Function();
easing.implementations = [ null, null, null, iterate, iterateWithDuration ];

and called with:
easing.call( "arg1", "arg2", "arg3" );
or
easing.call( "arg1", "arg2", "arg3", "arg4" );

@Nelle,

You could do it that way; ultimately, we're both doing the same thing - factoring out the branching logic such that the "worker" functions can have a very narrow set of concerns. In either case, I think we're on the right track.