Maintaining state within anonymous functions

I had a bit of fun with JavaScript on my personal blog over the weekend, pulling together a number of topics which I’ve covered here and in articles on SitePoint. The challenge was to provide a way of linking to any paragraph within a blog entry. The solution I came up with ended up using an unobtrusive JavaScript script, a bookmarklet and some CSS as well. You can read all of the gory details in the entry, but I’d like to dissect the bookmarklet further here.

The aim of this bookmarklet (entitled “add plink IDs”) is to find all textareas on the current page that appear to contain HTML and to then add ID attributes to any paragraph tags in the textareas that do not yet have them. For example, the following HTML:

 

<p>This is a paragraph.</p>

<p>This is another paragraph.</p>


Would become:



<p id="p-0">This is a paragraph.</p>

<p id="p-1">This is another paragraph.</p>

Here’s the full bookmarklet I used, indented for readability:

 

javascript:(function() {
  var tas = document.getElementsByTagName('textarea');
  for (var i = 0; i < tas.length; i++) {
    var ta = tas[ i ];
    var text = ta.value.replace('<p>', function() {
      if (typeof arguments.callee.counter == 'undefined') {
        arguments.callee.counter = 0;
      }
      return '<p id="p-'+arguments.callee.counter++ +'">';
    });
    ta.value = text;
  }
})();

It’s wrapped in an anonymous function call, a technique I described in my article on bookmarklets. It’s mostly pretty straight forward, but the interesting bit is the replace call which does the actual work. Here’s the code in question:



var text = ta.value.replace('<p>', function() {
  if (typeof arguments.callee.counter == 'undefined') {
    arguments.callee.counter = 0;
  }
  return '</p><p id="p-'+arguments.callee.counter++ +'">';
});

There are two tricks going on here. Firstly, JavaScript’s string replace method normally takes two arguments: the string (or regular expression) to find and the string to replace it with. However, in place of a replacement string it can be provided with a function which will be executed once for every replacement made. Here I’ve used an anonymous function as it is more concise.

The second trick is the arguments.callee.counter bit. The ‘argument’ object is a built-in JavaScript object that is only available inside functions. It represents the arguments that were passed to the function, and generally behaves just like a JavaScript array. However, it also provides a ‘callee’ property which refers to the actual function object itself (in JavaScript, even functions are objects). This feature is only really useful in anonymous functions where there’s no function name to refer to the function by.

Because JavaScript functions are objects, they can have properties. The if statement at the start of the anonymous function checks if the function’s callee property has been defined yet: if it hasn’t, it is initialised to 0. Then in the return statement the ++ (postincrement) operator is called on that property – it returns the property’s current value but increments it so that next time it will be one higher.

The end result is that the anonymous function is called once for every paragraph tag found in the textarea, returning a paragrah tag with a new ID attribute every time. The ‘counter’ property is simply used to maintain state in between function calls. In fact, this could be achieved equally as well using a global variable but it’s cleaner to keep the property “inside” the function which uses it to avoid unnecessary namespace pollution.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://liorean.web-graphics.com/ liorean

    Well, you aren’t really using a local value, there. Also, I believe you’re using an ineffective way to recur. As an example we can take the factorial function:

    
    
    (function(a){
        return (a>0)?
            (a*arguments.callee(--a)):
            1;
    })(4); // => 24

    This is an anonymous function expression, using a lookup of arguments and then a lookup of callee. Another method to do the same thing would be to use a named function expression, and use that local name as selfreference, producing only a single lookup of self.

    
    (function self(a){
        return (a>0)?
            (a*self(--a)):
            1; 
    })(4); // => 24
    
    

    And a clumsier third way would be to use a closure instead, requiring a lookup, scope jump, lookup for both a and f:

    
    
    (function(a,f){
        return (f=function(){
            return (a>0)?
                (a*f(--a)):
                1;
        })();
    })(4); // => 24
    
    
    

    Now, both of the first ways, if you apply them to your example, would create a member of the function object itself. If the function object happens to be stored in a variable, you could access it from the outside. However, if you implement it as a closure bound variable a la 3, you get it to be private but still persistent. Thus, double wrapping it in function expressions might ensure privacy. Your example might not need it, but I’m sure there are cases that does.

  • http://liorean.web-graphics.com/ liorean

    Hmm, butchered my nicely formatted samples. Oh well. Anyway I just say a mistake there:
    (a*f(–a)) should be (a–*f()) in the last example.