Wednesday, December 25, 2013

JavaScript Pearl: Function Properties

Functions in JavaScript are Objects. This we know.
A lesser spotted
 JavaScript function
with Properties 

We pass these objects around as arguments to other functions, we assign them to variables, set them as methods of other objects, and, ultimately, invoke them.
What we tend to forget (at least I do) is that as Objects JavaScript functions can also be the bearers of properties. 
There are a few ways we can use this fact to our advantage and a very cool example is given in Resig and Bibeault's "Secrets of the JavaScript Ninja".

The question they pose is this - if we're storing functions in an object of array, how can we prevent inserting the same function twice?
As Resig and Bibeault point out, one possibility is to simply iterate over the array and test each item to see if it's already part of the collection. There is a more interesting way to accomplish this. Directly below I provide a function that returns what looks like a simple array, but one which both doesn't add objects more than once, and doesn't have to iterate over the entire array. 

1:  function buildCache() {  
2:   var cache = [];  
3:   var cache_current_id = 0;  
4:   var old_push = cache.push;  
5:   cache.push = function() {  
6:    //since we can't actually run a forEach across the "arguments" object (it looks like an array, but it isn't)  
7:    Array.prototype.forEach.call(arguments,function(element) {  
8:     //first we check whether the element has an id - if it does, we skip adding it  
9:     if(element.cache_id === undefined) {  
10:      element.cache_id = cache_current_id;  
11:      logToDiv('adding a new function with the id of ' + cache_current_id);  
12:      cache_current_id++;  
13:      old_push.call(cache,element);  
14:     } else {   
15:      logToDiv('Function already exists with id ' + element.cache_id);  
16:     }  
17:    });  
18:   };  
19:   return cache;  
20:  }  

There are a couple of things going on here.
On line 2 I set up a simple array object - this will act as our function cache.
We then declare (line 3) a variable that will be used to both keep track of, and assign ids to, items that we will be adding to our cache.
On line 3 we save the standard push function attached to the cache array - we then (line 5) overwrite the push method so that for every element we test whether it has yet been assigned a cache_id (line 9) -- this tells us that we've already added this element to our cache. If the cache id is unassigned, we add an id as a property to the element and use the original push function (which we saved in "old_push") to push it on to our cache array. 
If we find a property with the name of "cache_current_id" then we know we've already added this element and we simply skip over it.   
No need to iterate. Pretty great. You can see this code running in a JS Bin here.
Note the log output. In the JS Bin I create two functions, and add them both to the cache twice. With both functions the first time I add them to the cache they are assigned an id and pushed on to the array, but the second time we attempt to push them on to the cache we're informed they're already there.

As a bonus, I've used function properties in my logToDiv function to deal with line numbering. I leave figuring it out as an exercise for the reader.

1:  //simple logger to show what's   
2:  function logToDiv(message) {   
3:   if(logToDiv.line_no === undefined) {  
4:    logToDiv.line_no = 1;   
5:   }  
6:   var message_div = document.createElement('div');  
7:   message_div.innerHTML = logToDiv.line_no++ + ': ' + message;  
8:   var log_div = document.getElementById('content');  
9:   log_div.appendChild(message_div);  
10:  }  


No comments:

Post a Comment

Note: Only a member of this blog may post a comment.