Search code examples
javascriptjqueryfunctionprocedural-programming

I keep everything in an external .js file. But not all functions are used on every page. Does this affect speed?


My app's JavaScript/jQuery is contained in an external scripts.js file. It generally looks like this:

$('document').on('ready', function() {
    giraffe();
    elephant();
    zebra();
});

function giraffe() {
    // code
}

function elephant() {
    // code
}

function zebra() {
    // code
}

giraffe() is only used for the view available at /animal/giraffe
elephant() is only used for the view available at /animal/elephant
zebra() is only used for the view available at /animal/zebra,

But all 3 are supposed to run on the view available /animal/all. This is a rudimentary example, but this is the reasoning behind having them all in one .js file, apart from keeping HTTP requests to a minimum.

My question is, does this affect the JavaScript rendering? Even though giraffe() isn't used (has no elements to work on) on /animal/zebra, it's still being called. Does js/jQuery ignore the function if it finds nothing to do? I'm sure the whole script is read, and that probably takes time. So, what's best way to handle this?

One solution

To avoid conflicts, I've created conditionals at the top of the js file to only run the functions that the active page needs:

$('document').on('ready', function() {
    var body = $('body');

    if( body.hasClass('giraffe') ) {
        giraffe();
    }

    if( body.hasClass('elephant') ) {
        elephant();
    }

    if( body.hasClass('zebra') ) {
        zebra();
    }
});

This is a little more verbose than I would like, but it is successful in keeping these functions modular/conflict free. I welcome an improvement on this solution.


Solution

  • It would certainly be better to run just the code you need. It's not that difficult; the only minor complexity is handling your /animals/all case. If you want to keep all your code in one file, you could do it this way:

    var animals = {
        giraffe: function() {
            console.log( "I'm a giraffe" );
        },
        elephant: function() {
            console.log( "I'm an elephant" );
        },
        zebra: function() {
            console.log( "I'm a zebra" );
        }
    };
    
    $(function() {
        var animal = location.pathname.split('/').pop();
        if( animal == 'all' ) {
            for( var animal in animals ) {
                animals[animal]();
            }
        }
        else if( animal in animals ) {
            animals[animal]();
        }
        else {
            console.log( "I'm a mystery animal" );
        }
    });
    

    You can actually test this code by going to URLs like this right here on Stack Overflow, and then pasting that code into the JavaScript console:

    https://stackoverflow.com/animal/giraffe
    https://stackoverflow.com/animal/elephant
    https://stackoverflow.com/animal/zebra
    https://stackoverflow.com/animal/ocelot
    https://stackoverflow.com/animal/all

    Update: OK, so you explained in the comment that the actual situation is a bit more complicated. I'll leave this code here in case it's useful for anyone, but for your situation you may be closer to what you need with the code you already have.

    WRT the question of what one of your animal functions will do when you're on a page it doesn't relate to, of course that depends on how it's coded. It may successfully do nothing, or it may have an error, right?

    Since we're talking about jQuery code, here are a couple of examples. Suppose you have code that's looking for an element by ID and then assumes that the element exists:

    var zebraElement = $('#zebra')[0];
    console.log( zebraElement.value );
    

    On a page where the #zebra element exists, this code will log its value attribute. (I'm assuming for discussion that it's an element that has a value, such as an input element.)

    But if #zebra is not present, then zebraElement is undefined, and attemping to access its value will fail and land in the debugger.

    OTOH, if you coded that like this:

    var $zebra = $('#zebra');
    console.log( $zebra.val() );
    

    it wouldn't fail if #zebra is missing, it would successfully print undefined without causing an error.

    Similarly, if you have code that uses $().each(), it will typically run without failure when the element(s) are missing, because it simply won't execute the callback function:

    $('.animal').each( function( i, e ) {
        console.log( $(e).val() );
    });
    

    If there are no elements with class="animal", then the console.log() call will never be reached. So there's no error, it just doesn't do anything.

    Depending on what you're doing, this can be a perfectly reasonable way to fire up only the behavior you need—simply make sure your code handles the missing DOM elements by cleanly doing nothing.

    Also be sure to read nick's answer for more insights.

    And one more update… In the comment you mentioned keying off classnames on the body element. A nice way to do that would be similar to the code example above. You don't need a conditional for each animal, just a loop and a single conditional:

    var animals = {
        giraffe: function() {
            console.log( "I'm a giraffe" );
        },
        elephant: function() {
            console.log( "I'm an elephant" );
        },
        zebra: function() {
            console.log( "I'm a zebra" );
        }
    };
    
    $(function() {
        var $body = $('body');
        for( var animal in animals ) {
            if( $body.hasClass(animal) ) {
                animals[animal]();
            }
        }
    });
    

    So, for example, if you have <body class="giraffe zebra"> it will call the animals.giraffe() and animals.zebra() functions.