An Example Of Overloaded Functions With Very Different Sub-Function Implementations
Posted September 14, 2010 at 10:32 AM by Ben Nadel
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:
| | | | | |
| | ![]() | | ||
| | | |
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.




