timmackay
timmackay

Reputation: 1024

Advanced Javascript initialisation

I'm having trouble deciphering the following Javascript initialisation statement:

(function(NAMESPACE) {
        NAMESPACE.nav = {};
        var nav = NAMESPACE.nav,

_init = false,
        _isNavOpen = false,
        _inner = document.getElementById('inner-wrap');

    // constants
    nav.CLASS = 'js-nav-open';
    nav.CLASS_READY = 'js-nav';
    nav.CONTAINER = '#nav';
    nav.DURATION = 400;
    nav.HAS_CSSTRANSITIONS = $('html').hasClass('csstransitions') && $('html').hasClass('csstransforms3d');

... ...

// toggle open/close
    nav.toggle = function(event) {
        event.stopPropagation();

        if(_isNavOpen && $('html').hasClass(nav.CLASS)) {
            nav.close();
        } else {
            nav.open();
        }

        // this is for the links
        if(event) {
            event.preventDefault();
        }
    };

}(PROJECT_NAME));

It seems unnecessarily complicated - calling (or setting?) 'nav' 3 times in 2 lines. Can somebody please explain what the point is of flipping it around like this?

Upvotes: 3

Views: 169

Answers (3)

Jackson Ray Hamilton
Jackson Ray Hamilton

Reputation: 9466

This is a common practice when using jQuery:

(function ($) {
    var div = $('#my-div');
    // Etc
}(jQuery));

Wrapping your script in a closure ensures that certain variables will have the values you expect them to.

For example, jQuery uses the $ for doing just about everything. Most people like to use $('do something') rather than jQuery('do something').

But say that you have another library on the page that also uses the global variable $.

By wrapping your code in the closure, you "reserve" the $ as jQuery's, and jQuery's alone. (When you pass in jQuery as the argument to closure, $ can only mean "jQuery," in the scope of this function.)


Similarly, in your example, you are reserving the NAMESPACE variable. Even if there were another variable called NAMESPACE, causing a racket somewhere else on the page, by passing in a variable at the end of your closure, you will be guaranteed that NAMESPACE will be the object you expect it to be (at least within the closure).

Say that you had a global variable called AbominableSnowman, but you wanted to use AS as a shortcut. By doing this:

var AS = "Apple Soup";

(function (AS) {
    AS.tellMeAboutSnowmen();
    alert(AS.snowballs);
}(AbominableSnowman));

Your code will still function as you intended. (Proof: http://jsfiddle.net/RUzZH/1/)


As for "flipping it around," it seems like the original programmer wanted to shorten NAMESPACE.nav down to nav. This was probably the best way of doing that.

An alternative (not recommended):

// It's best to limit your assignments to 1-per-line
// This kind of code isn't fun to debug, or even read
var nav = NAMESPACE.nav = {};

It doesn't seem like something worth fretting over. However, since this script interacts with NAMESPACE.nav quite often, it will be slightly, slightly faster to directly reference the .nav property with the nav variable. (This really is a micro-optimization, but in this case it's conveniently justifiable for a different reason [sake of clarity].)

Upvotes: 4

Nicole
Nicole

Reputation: 33197

Here's a line-by-line explanation (with headers just to break it up):

Setup:

// Create an anonymous function expression taking `NAMESPACE` as a parameter.
// Likely the *real* namespace will be passed to the function at the end
// with ... })(realnamespacetomodify);
(function(NAMESPACE) {

// Create the new part of the namespace.  Note that we are editing a reference
// so really this change happens on whatever object was passed in.
    NAMESPACE.nav = {};

// Create a local pointing to this new sub-namespace.  Probably just for
// convenience, also possibly for portability (if the name is used in closures,
// then those closures don't need to refer to NAMESPACE directly).
    var nav = NAMESPACE.nav,

Module definition:

// While nav refers to an object likely in global scope, nav itself can
// never be referred to from global scope because it is a local here.

// These variables are local here.  They can never be referred to by global scope.
    _isNavOpen = false,
    _inner = document.getElementById('inner-wrap');

// These variables, added to nav, can be accessed using the object that
// nav refers to in global scope (see the end).
    nav.CLASS = 'js-nav-open';
    ... 

// This function is also added to nav, therefore it can be accessed outside
    nav.toggle = function(event) {
        ...

        // This reference to _isNavOpen resolves because this function
        // is a closure, and binds variables outside its scope
        // to the function itself.  So even though _isNavOpen can't be
        // accessed globally, it can be accessed here, making it like
        // a private member of this namespace.
        if(_isNavOpen && $('html').hasClass(nav.CLASS)) {
            // nav is also bound by the closure and can be accessed here
            nav.close();
        } ...
    };

Use in global space:

}(PROJECT_NAME));

console.log(PROJECT_NAME.nav.CLASS); // "js-nav-open"
console.log(PROJECT_NAME.nav.toggle); // Function object

This is a module pattern. It is used for several reasons:

  • Code portability (not referring to global objects inside the module)
  • Scoping (avoiding assigning unnecessary vars to global namespace)
  • Visibility (hiding private access variables)

As for the first three lines themselves (your original question), they could refer to PROJECT_NAME directly, but it looks like it's been set up to aid code portability. You'll notice that the anonymous function itself never points to the real object (PROJECT_NAME). That means that you can copy and paste this part around and only change that reference in one place.

The other answer mentions scope, and while that's important too, it doesn't explain all benefits of this code, such as why it doesn't just directly refer to existing global variables. The scope-hiding benefits themselves are achieved with this part of the pattern:

(function() {
  ... // Anything set here is local, not global.

})();

Upvotes: 3

TGH
TGH

Reputation: 39248

This is an example of a JavaScript closure which is commonly used to create private scope and avoid having the objects pollute the global scope.

It is very common to create plugins this way to avoid conflicts with other functionality on the page as a result of variables with the same name etc. Essentially it's a mechanism for managing scope.

Upvotes: 4

Related Questions