JavaScript and Russian Dolls

In JavaScript, functions are variables which means they can be created and replaced at run time. Thanks to the pioneering efforts of Richard Cornford (Russian Doll Pattern, 2004), Peter Michaux (Lazy Function Definition pattern, 2007) Oliver Steele (One-Line Memoization, 2006) there are nifty techniques that exploit this capability.

First, a very simple example to illustrate the principle:-

var pushTheRedButton = function() {
    //reassign a new function to the variable pushTheRedButton
    pushTheRedButton = function() {
        //this line gets called on all subsequent visits</span>
        alert("Now look what you've done!");
    }
    //this line only gets called on the first visit</span>
    alert("Don't ever push this button again!");
}

pushTheRedButton(); //"Don't ever push this button again!"
pushTheRedButton(); //"Now look what you've done!"

I’ve conjured up a bunch of real life examples and organized them into three pattern types

1. Temporal – Functions that get modified based on passage of time or number of iterations.

Consider an application that calls a lengthy process when asked to shutdown. Sometimes the user will get impatient or uncertain and hit the button again before shutdown is complete. We could disable the button, but that’s not necessarily re-assuring to the user who doesn’t know what’s going on. Instead we can do this :-

system.shutdown = function() {
    system.shutdown = function() {
        alert("don't worry - we're already processing your shutdown request");
    }
    lengthyShutdownProcess();
}

system.shutdown();
system.shutdown(); //"don't worry - we're already processing your shutdown request"

This works great for shutdown because when the app is re-started the default shutdown function gets reloaded. But what if the lengthy process is a non-terminal one, such as a download? Subsequent downloads would simply display the “still downloading” message, which is wrong. We can fix this by defining the default download function in the object prototype and redefining the modified function at the instance level where it can be deleted by a callback when the download is finished:-

System.prototype.download = function(file) {
    this.download = function() {
         alert("still downloading");
    }
    requestDownload(file, {
        callback: function() {
            delete this.download;
        }
    });
}

Sometimes subsequent iterations of a function require more subtle modifications. The following is a URL object, designed to take components in object form and return the complete URL string on request. One problem is the queryParams portion of the string – the prefix for the first param pair needs to be a ‘?’ but for subsequent parameters pairs it must be a ‘&’. The entire URL object is quite long but I wanted to include it so others can run it. I’ve highlighted the lines where I’ve applied the function replacement pattern (note: this example uses the curry function which I introduced in a previous post):-

var URL = function(protocol, domain, queryParams) {
    this.protocol = protocol;
    this.domain = domain;
    this.queryParams = queryParams || {};
}

URL.prototype.paramsToString = function() {
    var stringArray = [];
    for (var prop in this.queryParams) {
        stringArray.push(this.printParam(prop));
    }
    delete this.printParam;//reset so prototype version used on first pass of next call
    return stringArray.join('');
}

URL.prototype.addParam = function(name,value) {
    this.queryParams[name] = value;
}

URL.prototype.printParam = function(param) {
    var queryParams = this.queryParams;
    var printAssignment = function(delimiter, prop) {
        return escape(delimiter + prop + "=" + queryParams[prop]);
    }
    this.printParam = printAssignment.curry('&amp;'); //define new function on instance that will be used on next pass
   return printAssignment.curry('?')(param); //used on this pass only
}

URL.prototype.toString = function() {
    return this.protocol + "://" +
    this.domain + this.paramsToString();
}

var googleURL = new URL('http','news.google.com',{q:'earthquake','geo':'California'});
googleURL.toString(); //"http://news.google.com?q=earthquake&geo=California"</span>

googleURL.addParam('as_nsrc','New York Times');
googleURL.toString(); //"http://news.google.com?q=earthquake&amp;geo=California&as_nsrc=New%20York%20Times"</span>

I’ll be the first to admit this is probably overkill. It would be perfectly fine to employ a ternary on the iteration index instead. However I think the use-case it illustrates is of value, and the solution offered may be of use to readers encountering similar problems. Let me know if you come up with a better example.

2. Conditional – functions discard conditional logic that will never apply to them

The inner text of a DOM element can be retrieved in one of two ways according to browser type.

 var myText = myDiv.innerText; //IE, chrome, safari</span>
var myText = myDiv.textContent; //firefox, chrome, safari</span>

Since the user can not switch browser without reloading the entire JavaScript library, it is safe to reassign the function to a more limited implementation based on the known browser capabilities.

 var getMyText = function(myDiv) {
    getMyText =
        myDiv.innerText !== undefined ?
            function(myDiv) {return myDiv.innerText} :
            function(myDiv) {return myDiv.textContent};

    return getMyText(myDiv);
}

This eliminates the need for condition checking every time the function is called. The return statement on the last line will only be invoked on the first pass.

In the above examples the savings are relatively small because the conditional test has a tiny footprint. But such tests are often expensive and multipart (if..else…else…else). Moreover, variables (including potentially bulky anonymous functions) declared in the original function are freed up for garbage collecting providing you’re careful not to reference them in the replacement function. Finally, removing unnecessary logic at runtime can improve the debugging experience.

3. Economical – functions that “rewrite” themselves to avoid repeating expensive processes.

Here is a Person object which includes a method to return the Person’s zodiac sign. This calculation is non trivial (ok, pretend it is please) so after the first pass we define a new method at the instance level which simply returns the result which we’ve locked into the function by closure.

By the way please go easy on my zodiacLookup object, yes it takes no account of timezone or place of birth. Those millisecond calcs were tricky enough as it was ;-)

var zodiacLookup = {
    1584000000:"Capricorn",
    4262400000:"Aquarius",
    6850800000:"Pisces",
    9442800000:"Aries",
    12121200000:"Taurus",
    14799600000:"Gemini",
    17564400000:"Cancer",
    20242800000:"Leo",
    22921200000:"Virgo",
    25513200000:"Libra",
    28108800000:"Scorpio",
    30700800000:"Sagittarius",
    31564800000:"Capricorn"
}

var Person = function(name, dateOfBirth) {
    this.name = name;
    this.dateOfBirth = dateOfBirth;
}

Person.prototype.getSign = function() {
    var testDate = new Date();
    testDate.setTime(this.dateOfBirth.getTime());
    testDate.setYear("1970");
    var dateInMs = +testDate;
    for (var prop in zodiacLookup) {
        if (dateInMs < prop) {
            var sign = zodiacLookup[prop];
            this.getSign = function() {
                return sign + " (the easy way)";
            };
            return sign + " (the hard way)";
        }
    }
}

var bob = new Person("Bob",new Date("August 5, 1970"));
bob.getSign(new Date()); //Leo (the hard way)
bob.getSign(new Date()); //Leo (the easy way)

This is a more elegant and lightweight alternative to the more familiar memoization pattern….

if(sign != null) {
    return /* do complex stuff */;
} else {
    return sign;
}
About these ads

17 thoughts on “JavaScript and Russian Dolls

  1. I will often rewrite functions inside themselves to prevent things that should only run once from being called again, mainly in terms of page scripts. I’ll have a minified/merged script that includes functions for each template, and page throughout the site…

    site.pageNameLoad = function() {
    site.pageNameLoad = function(){}; //prevent multiple calls

    site.templateName(); // load template

    // do other stuff for the page
    };

    In doing this I can create a cohesive web application, where the only output from the page is one script include, and one script tag with page options, localizations and a call to the pageNameLoad method. This does have a larger initial load time, than separate scripts, but means everything in script is pretty much loaded one time, and cached, and subsequent pages in the site/app load more quickly.

  2. thanks for the clear exemples around that technique : I knew it existed, but I never figured out in which case it was necesary

    from a performance point of view, the examples you gave could also work with a simple caching system, like :

    Person.prototype.getSign() {
    // check if answer was already given
    if( this.sSign )
    return this.sSign + ‘the easy way’;
    // calculation of sign here
    // register and give back sign
    return this.sSign = sign;
    }

    any reason I should obfuscate my code instead of using a caching system ? memory consumption perhaps ?

    • @jpvincent – thanks for the input. Yes that would work too – the downside is you have to invoke a conditional each time (and you need an extra variable)…aside from the minor performance hit, it makes debugging a bit clunkier IMO. Compare to Russian Doll pattern where you get to de-reference your unnecessary junk after the first pass

  3. Pingback: === popurls.com === popular today

  4. These are very entertaining and enlightening and I tend to make really heavy use of Javascript’s promiscuous polymorphism but I’d have to say that I’d not allow any of these examples in a code base I was in charge of.

    The issue is that it introduces state in a weird place where you don’t expect it. The code with just boolean variables might be a littel longer and a little less elegant, but it’s easier to understand and debug – and also you don’t get issues like seeing “loading” still if your load is interrupted, because you simply create a new instance of your class each time.

    Still, inspiring!

  5. Pingback: The Russian Doll Principle: re-writing functions at runtime « JavaScript, JavaScript « Netcrema – creme de la social news via digg + delicious + stumpleupon + reddit

  6. It is often used pattern, but keep in mind if you rewrite your constructors you should keep `prototype’ property, because `instanceof` and adding new properties in prototype chain wont work.

    See example:

    function F() {
    F = function () {};
    }

    var obj = new F();
    print(obj instanceof F); //false

    F.prototype.prop = true;
    print(‘prop’ in obj); //false

    At that example I lose reference to object referred by `F’ and that has terrible effect because I cannot add new properties via prototype chain.

    Regards for the good articles.

    • Asen, thanks for pointing this out (actually I just mentioned instanceof problem in my prototype post). In general I find JavaScript constructors do not respond well to functional applications – for example currying and composition will also create new functions which disrupts the prototype chain. This speaks to the fragile nature of Constructor prototype dependency.

      Also thank you for your excellent work btw ;-)

  7. Just a couple of observations:

    * There missing semicolons after many assignments.
    * The textContent function has a mistake of checking myDiv.innerText in boolean context. The problem is that when myDiv.innerText is empty string, “”, it evaluates to false.

    And so if innerText == “”, then with this:

    var getMyText = function(myDiv) {
        getMyText =
            myDiv.innerText ?
                function(myDiv) {return myDiv.innerText} :
                function(myDiv) {return myDiv.textContent};
        return getMyText(myDiv);
    }
    

    - when getMyText is called where myDiv.innerText == “”, it will get the function that returns myDiv.textContent. The problem with that is that for IE <= 8, if that happens the first time, then subsequent calls to that method name will always return undefined (because IE <= 8 does not have textContent).

    Good concepts though. I'm a fan of this pattern and use it a lot.

  8. Pingback: RPW 29/04/10: Javascript russian dolls, WebSockets, CSS3 pour IE | BrainCracking - Veille technologique sur les applications Web

  9. I’m using JS both for web development and desktop application testing (via TestComplete). These both involve capability sniffing and expensive initial setup operations. Up till now I had used globals then moved onto closures to achieve this. Now I am dolling up my scripts.

    I have read the Crockford and Zakas books this year but to me this is the Chuck Norris of all JS techniques and I commend you, sir.

  10. This is an interesting principal, but I just want to make sure that other understand that the original function will still exist if you referenced with another variable. The following is a modified form of the first example which illustrates that the original function still exists:

    var pushTheRedButton = function() {
    //reassign a new function to the variable pushTheRedButton
    pushTheRedButton = function() {
    //this line gets called on all subsequent visits
    alert("Now look what you've done!");
    }
    //this line only gets called on the first visit
    alert("Don't ever push this button again!");
    }, originalFn = pushTheRedButton;

    pushTheRedButton(); //"Don't ever push this button again!"
    pushTheRedButton(); //"Now look what you've done!"
    originalFn(); // "Don't ever push this button again!"
    originalFn(); // "Don't ever push this button again!"
    originalFn(); // "Don't ever push this button again!"

    With that said, I am confident that you un

  11. Pingback: Writing applications versus frameworks | KishoreLive

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s