Saturday, January 5, 2008

javascript : memoizing - caching functions result

The use of Javascript in a browser context requires to keep an eye on resources. Avoiding unnecessary variables and computation is one way to minimize a crash due to CPU overflow.We will see how we can reduce the number of computations by memoizing functions result.

A concrete example : setting css style


if you read the entry about setting the style thru javascript, you saw that the standard and IE way of naming the float attribute was different:
Standard : cssFloat
IE : styleFloat
Therefore, let's see the setStyle function again:
function setStyle(elm,prop,val) {
  if(prop=='opacity') 
    return setOpacity(elm,parseFloat(val));
  if(prop=='float')
    prop = (window.attachEvent) ? 'styleFloat' : 'cssFloat';

  prop = camelize(prop);
  elm.style[prop] = val;
  return elm;
}
As you can see, in order to use the proper naming for float, we use a ternary operator that looks for a IE method, attachEvent.
If we use this function many time, this computation is going to be done again and again.
Let see two ways to solve this problem:
-lazy setting:
var styleFloat=(window.attachEvent) ? 'styleFloat' : 'cssFloat';

function setStyle(elm,prop,val) {
  if(prop=='opacity')
     return setOpacity(elm,parseFloat(val));
  if(prop=='float')
     prop = styleFloat;
  prop = camelize(prop);
  elm.style[prop] = val;
  return elm;
}
There is not that much difference!
we just put the computation outside the function to have a variable holding the proper float naming for us, once and for good.
The other way is to use a dynamic definition:
function getStyleFloat() {
    //alert('first call');
    var float =(window.attachEvent) ? 'styleFloat' : 'cssFloat';
    this.getStyleFloat = function() {
       //alert('second call');
        return float;
    }
    return float;
}

function setStyle(elm,prop,val) {
  if(prop=='opacity')
     return setOpacity(elm,parseFloat(val));
  if(prop=='float') prop = getStyleFloat();
  prop = camelize(prop);
  elm.style[prop] = val;
  return elm;
}
Well, this is a lot of typing right?
The getStyleFloat can seem complex but in fact, it is very easy!
In order to use this dynamic definition pattern, you just need to create your function as you always do!
We just redefine the function by using a closure. Here this refers to the window object and just put the getStyleFloat function into the global space.
We are just 'overwritting ourself'!
the first time you call the function it will do the usual computation and all the following calls will just send you back the result, like a cache!
This is a rather trivial example but for heavy computation, this can be very usefull!

A second example : XMLHttpRequest


Here again, we have to face differences between IE and the rest of the world.
In that case, IE was the pioneer by creating the XMLHttpRequest and it is the rest of the world who has copied this functionality.
But IE, only for the windows platform, requires to use the ActiveX component, which is not available in all OS, that's why other browsers have created their own XMLHttpRequest.
Let's see how to switch between them:
function getXHR() {
   var xhr = '';
   if (window.XMLHttpRequest) {
      xhr  = new XMLHttpRequest();
   }
   else if (window.ActiveXObject) {
      xhr = new ActiveXObject('Microsoft.XMLHTTP');
   }
   return xhr;
}
so here again, we will do the following computation everytime we call the function.
And even worse, we are going to instantiate the xhr object several times!
We are therefore going to see how to cache the result and on top of that create a singleton (a singleton is a way to have only one and unique instantiation of a class during the course of the program):
function getXHR() {
   var xhr = '';
   if (window.XMLHttpRequest) {
      xhr  = new XMLHttpRequest();
   }
   else if (window.ActiveXObject) {
      xhr = new ActiveXObject('Microsoft.XMLHTTP');
   }
   this.getXHR = function() {
       return xhr;
   }
   return xhr;
}
And voila!
The first call to the function will do the computation and all the other call will send back the result from the first call!

Conclusion


You can find many use to this dynamic definition pattern, especially in order to deal with cross-browser issues that we face in javascript!
This can also be used to create a singleton like above or to cache some heavy computation you may need to do for animations or else.

2 comments:

Anonymous said...

A really really good tip. Optimizing scripts now!

Paw said...

I think Greg said it all.

GREAT blog btw...
Keep up the good work so that all of us new-comers to JavaScript can evolve :)