admin管理员组

文章数量:1291061

I'm looking for a way to conditionally load and keep the execution order of some javascript files (external and internal) without any library dependency. Basically, what I want to do is load them up only if the browser supports localStorage.

Here's basically my shell:

if (window.localStorage) {
//load up JS only if it's needed
    var body = document.getElementsByTagName('body')[0],
        js1 = document.createElement('script'),
        js2 = document.createElement('script'),
        js3 = document.createElement('script'),
        js4 = document.createElement('script'),
        js5 = document.createElement('script');

    js1.src = '.4.2/jquery.min.js';
    js2.src = '';
    js3.src = 'my_file1.js';
    js4.src = 'my_file2.js';
    js5.src = 'my_file3.js';

    body.appendChild(js1);
    body.appendChild(js2);
    body.appendChild(js3);
    body.appendChild(js4);
    body.appendChild(js5);
} else { 
    //no localStorage support, display messaging
}

I've tried dynamically adding script nodes via createElement/body.appendChild but those don't seem to work.

Is there an easy way to achieve this? Right now everything works, but IE6 and IE7 folks download script they aren't even executing, which is what I want to fix.

I'm looking for a way to conditionally load and keep the execution order of some javascript files (external and internal) without any library dependency. Basically, what I want to do is load them up only if the browser supports localStorage.

Here's basically my shell:

if (window.localStorage) {
//load up JS only if it's needed
    var body = document.getElementsByTagName('body')[0],
        js1 = document.createElement('script'),
        js2 = document.createElement('script'),
        js3 = document.createElement('script'),
        js4 = document.createElement('script'),
        js5 = document.createElement('script');

    js1.src = 'http://ajax.googleapis./ajax/libs/jquery/1.4.2/jquery.min.js';
    js2.src = 'http://www.google./jsapi';
    js3.src = 'my_file1.js';
    js4.src = 'my_file2.js';
    js5.src = 'my_file3.js';

    body.appendChild(js1);
    body.appendChild(js2);
    body.appendChild(js3);
    body.appendChild(js4);
    body.appendChild(js5);
} else { 
    //no localStorage support, display messaging
}

I've tried dynamically adding script nodes via createElement/body.appendChild but those don't seem to work.

Is there an easy way to achieve this? Right now everything works, but IE6 and IE7 folks download script they aren't even executing, which is what I want to fix.

Share Improve this question edited Oct 19, 2010 at 23:23 magenta asked Oct 19, 2010 at 22:57 magentamagenta 4011 gold badge5 silver badges6 bronze badges 2
  • @Crowder Gulp, I didn't know that. I hope the IE users will forgive me. :) – Šime Vidas Commented Oct 19, 2010 at 23:23
  • @Crowder Well, I killed the joke, I think it would be lame to repost a milder version. :) – Šime Vidas Commented Oct 19, 2010 at 23:47
Add a ment  | 

3 Answers 3

Reset to default 6

Adding script nodes should work just fine. Because those scripts will execute asynchronously to the code adding them, you'll need to give them a callback to call to do the next thing in order. E.g.:

if (window.localStorage) {
    // Load the local storage stuff; once loaded, it'll call
    // `doTheNextThing`
    var script = document.createElement('script');
    script.type = "text/javascript";
    script.src = /* ... the URL of the script ... */;
    document.body.appendChild(script); // Or append it to `head`, doesn't matter
                                       // and `document.body` is convenient
}
else {
    // Skip loading it
    setTimeout(doTheNextThing, 10);
}

function doTheNextThing() {
    // ...
}

...where the dynamic script you're loading for the localStorage stuff call doTheNextThing after it loads — so in the case where there's localStorage, the dynamically-loaded script calls doTheNextThing but in the case where there isn't, the code above does. Note that I made the call from the code above asynchronous (via setTimeout) on purpose: Making it always asynchronous regardless of how it gets called reduces your odds of missing bugs (e.g., adding something that relies on it being called synchronously and then forgetting to test that minor change on IE).

Update: The above assumes you're in control of the script you're loading, but you've clarified that you're not. In that case, what you need to do is load the scripts one at a time and poll for the feature that they provide (usually a property on the window object, like window.jQuery), something like this (untested):

// Load the script designated by `src`, poll for the appearance
// of the symbol `name` on the `window` object. When it shows
// up, call `callback`. Timeout if the timeout is reached.
function loadAndWait(src, name, timeout, callback) {
    var stop, script;

    // Do nothing if the symbol is already defined
    if (window[name]) {
        setTimeout(function() {
            callback("preexisting");
        }, 10);
    }
    else {
        // Load the script
        script = document.createElement('script');
        script.type = "text/javascript";
        script.src = src;
        document.body.appendChild(script);

        // Remember when we should stop
        stop = new Date().getTime() + timeout;

        // Start polling, long-ish initial interval
        setTimeout(poll, 150);
    }

    function poll() {
         if (window[name]) {
             // Got it
             callback("loaded");
         }
         else if (new Date().getTime() > stop) {
             // Time out
             callback("timeout");
         }
         else {
             // Keep waiting, shorter interval if desired
             setTimeout(poll, 75);
         }
    }
}

...which you'd use like this for the jQuery load:

loadAndWait(
    "http://ajax.googleapis./ajax/libs/jquery/1.4.2/jquery.min.js",
    "jQuery",
    10000, // ten seconds or whatever
    function(result) {
        // ...do the next one if result !== "timeout"
    }
 );

You can either nest calls to loadAndWait in each of the previous calls' callbacks, or use an array and counter:

loadThese(
    [
        {   src:    "http://ajax.googleapis./ajax/libs/jquery/1.4.2/jquery.min.js",
            symbol: "jQuery"
        },
        {
            src:     "http://the-next-one",
            symbol:  "nextSymbol"
        }
    ],
    doTheNextThing
);

function loadThese(scripts, callback) {
    var index = 0;
    run("okay");

    function run(result) {
        var entry;

        if (result === "timeout") {
            callback(result);
        }
        else if (index < scripts.length) {
            entry = scripts[index++];
            loadAndWait(entry.src, entry.symbol, 10000, run);
        }
        else {
            callback("loaded");
        }
    }
}

There, loadThese sets up a loop using run to load each script in turn.

All of the above is pletely off-the-cuff and can probably be tightened and bullet-proofed, but you get the idea.

Off-topic, but my question is: Is there really so much code that it's a problem for the browsers that can't use it to load it? Barring the files getting a lot bigger, you'll actually slow down your site for users with advanced browsers without gaining much of anything on the others. Below a certain size, the overhead of connecting to the server to retrieve the script is as big a factor as transferring it. Is the extra stuff 50k of code? I'd do some benchmarking to test whether it's really necessary... Perhaps it is (perhaps you already have!), but it's worth just mentioning...

Off-topic update: In your updated question, you list five separate scripts you'd be downloading if localStorage is supported. Even assuming you're getting all five from various CDNs, that's a lot of individual script requests (whether done in the usual way or as above), each of which has to be processed one at a time. That's a page load performance issue waiting to happen. Despite (possibly) losing the benefits of CDNs and existing caching, you might look at grabbing all of those scripts, bining them, and hosting your bined version in a single file. See "Minimize HTTP Requests" in the YUI performance "rules" (I prefer the term "guideline", but whatever). It would also simplify your dynamic loading.

You can use the bination of onload and closure function. Something like the following:

function loadScripts(index) {
    return function () {
        var e = document.createElement('script');
        e.src = scripts[index];
        document.body.appendChild(e);

        if (index + 1 < scripts.length) {
            e.onload = loadScripts(index + 1)
        }
    };
}

And invoke it like this:

loadScripts(0)();

I've e to use this code. Both main functions (addEvent and load_javascript) are found on the web.
I wasn't trying to reduce download size, though: this is the only way I could load resources. So, maybe the idea proposed by Šime Vidas makes sense for you.

addEvent = function(elm, evType, fn, useCapture) {
    //Credit: Function written by Scott Andrews
    //(slightly modified)
    var ret = 0;

    if (elm.addEventListener) {
        ret = elm.addEventListener(evType, fn, useCapture);
    } else if (elm.attachEvent) {
        ret = elm.attachEvent('on' + evType, fn);
    } else {
        elm['on' + evType] = fn;
    }

    return ret;
};


    var left_to_load = 0;
    function init() {
        --left_to_load;
        if (left_to_load > 0) {
            return;
        }

        // all scripts are loaded now
        // proceed with your logic
    }

    // load js file and call function when done
    function load_javascript(src, callback) {
        var a = document.createElement('script');
        a.type = 'text/javascript';
        a.src = src;
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(a, s);

        ++left_to_load;
        addEvent(a, 'load', callback, false);
    }



load_javascript('url1', init);
load_javascript('url2', init);
...

本文标签: lazy loadingconditionally load javascript (external and internal) and keep execution orderStack Overflow