Closures and executing JavaScript on page load

Over on my other blog I’ve just published a new technique for executing a piece of JavaScript once a page has finished loading. Here’s the code:


function addLoadEvent(func) {
  var oldonload = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = func;
  } else {
    window.onload = function() {
      oldonload();
      func();
    }
  }
}

addLoadEvent(nameOfSomeFunctionToRunOnPageLoad);
addLoadEvent(function() {
  /* more code to run on page load */ 
});

My other post has a run-down of why this technique is needed and an explanation of how it works, but I’d like to expand on that here by talking about the way the above code uses a JavaScript language feature known as a closure.

A closure consists of a function along with the lexical environment (the set of available variables) in which it was defined. This is a remarkably powerful concept, and one commonly seen in functional programming languages such as JavaScript. Here’s a simple example of closures in action:


function createAdder(x) {
  return function(y) {
    return y + x;
  }
}

addThree = createAdder(3);
addFour = createAdder(4);

document.write('10 + 3 is ' + addThree(10) + '
'); document.write('10 + 4 is ' + addFour(10));
createAdder(x) is a function that returns a function. In JavaScript, functions are first-class objects: they can be passed to other functions as arguments and returned from functions as well. In this case, the function returned is itself a function that takes an argument and adds something to it.

Here's the magic: the function returned by createAdder() is a closure. It "remembers" the environment in which it was created. If you pass createAdder the integer 3, you get back a function that will add 3 to its argument. If you pass 4, you get back a function that adds 4. The addThree and addFour functions in the above example are created in this way.

Let's take another look at the addLoadEvent function. It takes as its argument a callback function which you wish to be executed once the page has loaded. There follow two cases: in the first case, window.onload does not already have a function assigned to it, so the function simply assigns the callback to window.onload. The second case is where the closure comes in: window.onload has already had something assigned to it. This previously assigned function is first saved in a variable called oldonload. Then a brand new function is created which first executes oldonload, then executes the new callback function. This new function is assigned to window.onload. Thanks to the magical property of closures, it will "remember" what the initial onload function was. Further more, you can call the addLoadEvent function multiple times with different arguments and it will build up a chain of functions, making sure that everything will be executed when the page loads no matter how many callbacks you have added.

Closures are a very powerful language feature but can take some getting used to. This article on Wikipedia provides more in-depth coverage.

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://www.xDevDesign.com/ xDev

    Excellent work! I had a problem understanding what was going on from your weblog. This example you posted here clears up what’s happening when passing functions.

    Check out this link:
    http://w3future.com/html/stories/callbacks.xml

    There’s some cool javascript going on over there. I first found the site from his article Higher Order Programming: http://w3future.com/html/stories/hop.xml
    Seems he’s using some of the same concepts you pointed to great effect. Check out this example:
    http://w3future.com/html/orgcharts.html
    View source on that puppy! I have never seen javascript code like that in my life. He’s passing functions like ther’s no tomorrow, even passing arrays with functions as indexes.

  • http://www.phppatterns.com HarryF

    Excellent! Just cleared up a problem I’ve been having with XMLHttpRequest as well, when assigning the the onreadystatechange handler – if I pass a closure I can stay in touch with the original object.

  • http://boyohazard.net Octal

    Thanks Simon. Your script should help me with the same problem of adding more than one callback to window.onload

  • david currey

    I have been struggling with some code relating to this for a few weeks now, amd found:

    http://jibbering.com/faq/faq_notes/closures.html
    to be a good explanation of closures too.

  • ppk

    Interesting. Thanks.

    But is your example really a closure? The addLoadEvent() function doesn’t return a function, it just happens to set a function.

    Or maybe I don’t understand closures too well yet.

  • Robert Sayre

    http://www.crockford.com/javascript/javascript.html

    If anyone reading this intrigued, check out Douglas Crockford’s pages on JS. They show how to implement private methods and variables using closures, among other things.

  • Jan Schneider

    Konqueror, while claiming to almost completely support ECMA script, unfortunately just misses these closures, making a cross-platform use impossible if you can’t rely on Gecko on the client side. It simply “forgets” its environment when anonymous functions are called. :-/

  • Junbao

    Good Morning,
    I was going though ‘Designing without tables’ book, trying to figure a way to create a frames function with CSS.
    I am looking to have my nav bar on the left, maybe a banner on top and a ‘main frame’ that changes.
    I have done this statically, but I am looking for places to learn ways to make the main content change using the links on the left.
    Any suggests would be appreciated – Junbao

  • Brett Merkey

    None of the books I have go into the methods Willison and others talk about. The methods seem so abstracted and incomprehensible that applying them may make an application more expensive to maintain or even brittle.

    Example: I tried the code here to something I am working on (iteration on form labels to attach field-level Help provided by a writer in an external file) and it worked. I then added an empty event handler to the BODY tag — onload=””. This killed the fine Willison solution. Yes, we shouldn’t do that, but that is exactly what happens when you are working on collaborative development teams or working within the constraints of “helpful” templating systems.

    We would need a Willison to keep things straight — and we don’t have one!

    Brett

  • l.m.orchard

    ppk: The closure involved here isn’t returned – it’s set as the window.onload event handler. The function created here has access to the oldonload variable in the environment at the time of its creation, thus a closure.

  • Jeremy Dunck

    ppk:
    The part that Simon is referring to as a closure is this:

    window.onload = function() {
    oldonload();
    func();
    }

    That is, he is wrapping the old onload function with another function, and also calling the new function.

    In this case, I’m not sure that closure is the key here.

    I say this because nothing in the environment at the time of closure is pertinent to the event handling later done.

    But it is a closure, anyway, as all functions in JS are. ;-)

    This technique is very similar to .Net’s Multicast Delegate model.

  • test

    test

  • http://www.sitepoint.com AlexW

    Did your test work? sheeeesh! *slaps forehead*

    The onload thing has been a huge issue for me of late. I’ve stacking up functions from different includes and often for different browsers, tryting to make sure nothing is doubled up or left out. This approach simplifies thing tremendously.

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

    “Konqueror, while claiming to almost completely support ECMA script, unfortunately just misses these closures, making a cross-platform use impossible if you can’t rely on Gecko on the client side. It simply “forgets” its environment when anonymous functions are called. :-/” – Jan Schneider

    Is that so? Safari 1.2 (WebKit/WebCore v. 125.7, having a KJS derived scripting engine) supports closures and callback functions. Safari 1.0 and OmniWeb 5 (both based on WebCore v. 85) supports closures and sending functions as values, but don’t seem to support callbacks, at least not for the setTimeout function. So, does Konqueror really not support closures, or is the support for callbacks that is lacking?

    A testcode you can run:

    javascript:var a=”global”,b=a,c=b,fn=(function(b,c){b=”outer”;c=b;return function(c){c=”inner”;return [a,b,c];}})();alert(‘Should be [global,outer,inner]:n’+fn());

    It uses one closure, and returns a function as a value, so if the value returned is [global,outer,inner] you know that both those works. If it returns [global,global,inner] you know it doesn’t handle closures. If it gives an error, it likely don’t support functions as values.

    It doesn’t test if callbacks are supported, however, because testing that would require per-native-function testing as well as one test for custom functions.

  • http://cross-browser.com MikeFoster

    Altho a short article, it obviously has provoked much good thinking, and so the article has served its purpose well.

    Everyone should definitely read the comments here, at Simon’s blog, and also the comments on Eric Lippert’s related blog post.

    I had quite a bit to say, but after reading all the comments I think it has all been said… almost ;-)

    Thanks for provoking us to think, Simon :-)
    Mike Foster

  • Richard Cornford

    Presented with a similar problem to that addressed by – addLoadEvent – above, but with an additional need to pass arguments to the function called onload, I wrote a slightly different closure-based implementation. I May as well post the code here for comparison.


    /**
    *

    Title: Initializer.

    *

    Description: Common Onload initialisation interface.

    *

    Copyright: Richard Cornford 2004

    * @author Richard Cornford
    * @version 1.0
    */
    /**
    * A global function named - initializeMe - is created that can be used
    * to arrange the execution of an arbitrary number (but probably
    * implementation limited to the order of about 1500) of functions from
    * the - onload - handler of a web page. The intention being to allow an
    * indefinite number of other scripts to use a common interface for
    * triggering their onload initialisation functions.
    *
    * NOTE: This function must be included before any code that attempts
    * to use it.
    *
    * The - initializeMe - function is called with a reference to a
    * function object as its first argument, and up to 4 optional
    * additional arguments:-
    *
    * initializeMe(functRef, "idOfElement", "nameOfForm");
    *
    * - The function reference and any additional arguments are stored in a
    * function stack, the base of which is assigned as to window.onload
    * handler (or using W3C DOM addEventListener or IE attachEven, if
    * available) and when the onload event is triggered the execution of
    * the base of the function stack results in the execution of the
    * function passed as the first argument, followed by the next function
    * in the stack (which executes its function argument, And so on until
    * the stack is excused. The execution of functions passed to the -
    * initializeMe - function is in the order in which they are passed to
    * the - initializeMe - function (first in, first executed).
    *
    * The function object passed as - funcRef - is called with its first
    * argument being the (onload) - event - object and the values
    * originally passed to the call to - initializeMe - as the optional 2nd
    * to 5th arguments as its 2nd to 5th arguments (in the same order). So,
    * in the example call to - initializeMe - above, the function object
    * passed by reference as the first argument would have the pattern:-
    *
    * function(event, idString, nameString){
    * ...
    * }
    *
    * - and the two string arguments passed to - initializeMe - would be
    * passed on when it was called during the onload event.
    *
    * Note: The - event - object passed to the calls to the - funcRef -
    * functions is browser normalised with - (ev?ev:global.event) - prior
    * to the call to the function, so these function do not have to
    * normalise the event objects themselves, but they also cannot make
    * decisions about the DOM in use from inferences about their - event -
    * argument.
    */
    var initializeMe = (function(){
    var global = this;
    var base = null;
    var safe = false;
    var listenerType = (global.addEventListener && 2)||
    (global.attachEvent && 3)|| 0;
    function getStackFunc(funcRef, arg1,arg2,arg3,arg4){
    var next = null;
    function l(ev){
    funcRef((ev?ev:global.event), arg1,arg2,arg3,arg4);
    if(next)next = next(ev);
    return (arg1 = arg2 = arg3 = arg4 = funcRef = null);
    };
    l.addItem = function(d){
    if(next){
    next.addItem(d);
    }else{
    next = d;
    }
    };
    return l;
    };
    return (function(funcRef, arg1,arg2,arg3,arg4){
    if(base){
    base.addItem(
    getStackFunc(funcRef, arg1,arg2,arg3,arg4)
    );
    }else{
    base = getStackFunc(funcRef, arg1,arg2,arg3,arg4);
    }
    if(!safe){
    switch(listenerType){
    case 2:
    global.addEventListener("load", base, false);
    safe = true;
    break;
    case 3:
    global.attachEvent("onload", base);
    safe = true;
    break;
    default:
    if(global.onload != base){
    if(global.onload){
    base.addItem(getStackFunc(global.onload));
    }
    global.onload = base;
    }
    break;
    }
    }
    });
    })();
    //^^ : The inline execution of the outermost function expression.

    Incidentally, Konqueror 2 was incapable of recognising inner function declarations and closures did not work with it. But its ECMAScript implementation was described as “experimental” and the failure of that experiment was obviously recognised because Konqueror 3 doesn’t seem to have any problem with closures, inner functions or callback.

  • Paul D

    Thank you, Richard – works like a charm! Successfully loading 2 events in NN and IE

  • Adam

    Dude, aswsome I had a tab solutiona and menu that bothed used an onload method. This solution is absolutly fantastic. Thank you for all the help.

  • Anonymous

    Trust a Javascript wizard to come up with a popup that defies Firefox’s popup blocking…

  • http://www.dustindiaz.com polvero

    Granted it’s closing in on 18 months since this was written, I’m truly amazed on just the logic of this function.

    It takes into consideration multiple developers working on the same project. It also assumes everyone on board is working with the same philosophy (which teams should).

    This is, in fact, a function for the ages.

  • Russell Lively

    Can anybody tell me why this technique doesn’t seem to work when I try to use it in a loop of all elements obtained through getElementsByTagName ? The new event function registration still clobbers the existing event. I was able to make a small simple test page that worked, but I can’t seem to get this to work:

  • Devid

    Thank you, and everyone who contributed to the creation of this website.

  • John

    How can I modify this example if I have arguments to pass?

  • end-user

    I found that I was getting an error in IE6, though I don’t know why. I found that it was fixed with if(func)window.onload = func;

  • Ashish Chauhan

  • dudes…help asap!

    being new at this…Im sure this has to be possible.
    I want to make new windows appear in my main page everytime you click on a left tab or something. Any ideas
    ps.-im trying to use .css

  • Anonymous


  • zaldun

    Hi, very nice post, i am following that to load a function when page is loaded, so as to check if some kind of element id is already present at document tree of elements, by using document.getElementById;

    the purpose is to create an unique ID to set to a block also created at onload, so the function generates IDs (‘id’+0, ‘id’+1, …) till finding an unused one;

    the problem with addLoadEvent function as i see, is that though it is built to be nice to other javascript at page, it could happen that the other javascript code might not be as nice, and overwrite window.onload after addLoadEvent was used;

    the idea i am commenting is related to use javascript on pages to embeed some content, whichever the other javascript snipets that could be also present are;

    so my question is if the only way to avoid the case of overwriting is running addLoadEvent after any other script touches windows.onload

    wow, sorry for the length of this, just trying to explain the context, and thanks in advance for comments, ideas, suggestions, …

  • Fleejay

    John – I too was searching for a way to figure out how to pass parameters to a function. You can do something like:

    addLoadEvent(function()
    {
    myFunc(myParam1, myParam2);
    });

  • Will

    Hello,

    I’m trying to utilize your JS to stop a not implemented error. I still can’t get it to work. Here’s a page if you have time to check it out.

    https://www947.ssldomain.com/wtomlinson/Store/productdetail.cfm?PID=165

    Thanks,
    Will

  • Hal Diggs

    Thanks Fleejay for your simple parameter implimentation. Its works in IE6 and NetScape… rock on.

    Thanks again,
    Hal

  • Jon

    And old post…but thanks! – this really helped me out.

  • Jake

    the only problem remaining is when the html guy decides to add an old fashioned

    it clobbers window.onload… and all the good stuff you’ve set up does not get run!

    Any ideas?

  • Jake

    the only problem remaining is when the html guy decides to add an old fashioned

  • Jake

    the only problem remaining is when the html guy decides to add an old fashioned

    < body onload …
    it clobbers window.onload… and all the good stuff you’ve set up does not get run!

    Any ideas?

    (third time trying to post the html tag!)

  • phpPete

    Jake;

    I have the same problem — an application I am supporting uses HierMenus and the HierMenus code implements body onload extensively. In my case, and I suspect most cases, you can’t apply Wilson’s solution to the body element since the DOM is either not fully constructed, hence the body element does not yet exist, or the document is already loaded and the body onload event has already fired. The only way I was able to get around it is to embed a function call inline inside script elements immediately before the closing html tag. It’s ugly and not scalable and if anyone else knows of an alternative I’d love to hear it.

  • phpPete

    One other possibility is to check the document’s readystate property and execute your function upon status “complete”:

    // example
    document.onreadystatechange = myFunction;


    function myFunction{
    if( document.readystate == “complete”){…}
    }

  • Anonymous

    function trans()
    {
    alert(good’);
    document.formmm.submit();
    }

  • Charlie

    Dear Sir

    I am using Opera 8 on Windows XP.

    I have embedded a Quick Time movie in the page. I have created a simple button that calls the movie to play:

    object.Play();

    When I press the reload button at the top of the Opera browser and try clicking on the play button again. The movie does not play. If I close Opera, and open it again and press play, it works. Infact I can press play several times aslong as I do not reload or refresh the browser.

    In Opera 9, everything works fine, but I need to display this application on all versions of all browsers.

    Do you know what might be causing the prolem. In the error console, I get:

    name: TypeError
    message: Statement on line 46: Type mismatch (usually a non-object value used where an object is required)

    Please could you help me, I am tearing my hair out here!

    Thanks in advance…

    e-mail: cdesign@btinternet.com
    mobile: 07881987197

    Charlie

  • Mike L

    I just wanted to say thank you for providing this code and the explanation.

    I have been fighting with some code on a page that I don’t have the ability to insert into the ‘body’ tag, and your code allowed me to overcome that problem.

    Pasted it in, added the function name, and saved it.

    It wourked perfectly, the very first time.

    Thanks again.

  • Meteorbites

    I’m just wondering how it differs from using your sample below. (Figure 1.) on (Figure 2). Figure 2 achieved the same thing as Figure 1 with lesser code.

    Figure 1

    
    function addLoadEvent(func) {
        var oldonload = window.onload;
        if (typeof window.onload != 'function') {
            window.onload = func;
        } else {
            window.onload = function() {
                if (oldonload) {
                    oldonload();
                }
                func();
            }
        }
    }
    
    addLoadEvent(function() {
        document.body.style.backgroundColor = 'yellow';
    })
    addLoadEvent(function() {
        document.body.appendChild(document.createTextNode('Hungry'));;
    })
    addLoadEvent(function() {
        document.body.appendChild(document.createTextNode(' monkeys!'));;
    })
    
    
    window.onload=function()
    {
      document.body.style.backgroundColor = 'yellow';
      document.body.appendChild(document.createTextNode('Hungry'));
      document.body.appendChild(document.createTextNode(' monkeys!'));  
    }
    

    email: meteorbites@yahoo.com

  • Kylir Horton

    Amazing. This is beautiful. I’ve used this in many locations on a web application I’m working on and it has worked flawlessly and solved many problems which would have otherwise broken the application. Thank you very much for your work!

  • buduznat

    thank you! i was banging my head on a problem for days :) great work!

  • Allen Sanford

    Just in case you ar wondering no this does not work in firefox 3

  • Anonymous

    ff

  • johnnyboyNiumata

    I love this latest code works great…..

    I use it to load flash movies in after the rest of the page in conjunction with Robert Nymans flash loading script

  • myxo

    THANKS! Saved my ass.

  • Solmaz

    so useful… thank you.