The window Object & Global Variables

Now that we're using this fancy self-executing function, we don't have access to RepLogApp anymore:

71 lines web/assets/js/RepLogApp.js
(function() {
var RepLogApp = {
... lines 3 - 50
};
... lines 52 - 69
})();

How can we fix that? Very simple. Instead of var RepLogApp, say window.RepLogApp:

71 lines web/assets/js/RepLogApp.js
(function() {
window.RepLogApp = {
... lines 3 - 50
};
... lines 52 - 69
})();

Back in the template, I'll delete the console.log() for Helper:

78 lines app/Resources/views/lift/index.html.twig
... lines 1 - 64
{% block javascripts %}
... lines 66 - 69
<script>
console.log(Helper);
... lines 72 - 75
</script>
{% endblock %}

And then go back and refresh. It works! No error in the console, and delete does its job!

What is this window?

So what the heck just happened? Here's the deal: when you're executing JavaScript in a browser - which for you is probably always - you always have access to a global window variable. In fact, it's even more important than that. This window variable holds all of the global variables. What I mean is: if you set a key on the window object, like RepLogApp, this becomes a global variable. That means you can reference RepLogApp from anywhere else, and this is actually referencing window.RepLogApp. More on that in a second.

Passing Yourself Global Variables

Inside of our self-executing function, we - of course - also have access to any global variables, like window or the $ jQuery variable. But, instead of relying on these global variables, you'll often see people pass those variables into the function. It's a little weird, so let's see it.

Right now, inside of our self-executing function, we're using two global variables: window and $, for $.ajax, for example:

71 lines web/assets/js/RepLogApp.js
(function() {
window.RepLogApp = {
... lines 3 - 21
handleRepLogDelete: function (e) {
... lines 23 - 35
$.ajax({
... lines 37 - 44
});
},
... lines 47 - 50
};
... lines 52 - 69
})();

At the bottom of the file, between the parentheses, reference the global window and jQuery variables and pass them as arguments to our function. On top, add those arguments: window and $:

71 lines web/assets/js/RepLogApp.js
(function(window, $) {
window.RepLogApp = {
... lines 3 - 69
})(window, jQuery);

Now, when we reference window and $ in our code, we're no longer referencing the global objects directly, we're referencing those arguments.

Why the heck would you do this? There are two reasons, and neither are huge. First, you can alias global variables. At the bottom, we reference the jQuery global variable, which is even better than referencing $ because sometimes people setup jQuery in no conflict mode, where it does not create a $ variable. But then above, we alias this to $, meaning it's safe inside for us to use that shortcut. You probably don't have this problem, but you'll see stuff like this in third-party libraries.

Second, when you pass in a global variable as an argument, it protects you from making a really silly mistake in your code, like accidentally setting $ = null. If you do that now, it'll set $ to null only inside this function. But before, you would have overwritten that variable globally. It's yet another way that self-executing blocks help to sandbox us.

Fun with window

Ok, back to this mysterious window variable. Inside index.html.twig, console.log() window:

78 lines app/Resources/views/lift/index.html.twig
... lines 1 - 64
{% block javascripts %}
... lines 66 - 69
<script>
console.log(window);
... lines 72 - 75
</script>
{% endblock %}

This is pretty cool, because it will show us all global variables that are available.

And Boom! This is a huge object, and includes the $ variable, jQuery, and eventually, RepLogApp.

But notice what's not here. As expected, there is no Helper.

Forget var? It goes Global!

Now, go back into RepLogApp, find Helper, and remove the var:

71 lines web/assets/js/RepLogApp.js
(function(window, $) {
... lines 2 - 55
Helper = {
... lines 57 - 68
};
})(window, jQuery);

You've probably been taught to never do this. And that's right! But you may not realize exactly what happens if you do.

Refresh again and open the window variable. Check this out! It's a little hard to find, but all of a sudden, there is a global Helper variable! So if you forget to say var - which you shouldn't - it makes that variable a global object, which means it's set on window.

There's one other curious thing about window: if you're in a global context where there is no this variable... then this is actually equal to window:

78 lines app/Resources/views/lift/index.html.twig
... lines 1 - 64
{% block javascripts %}
... lines 66 - 69
<script>
console.log(window === this);
... lines 72 - 75
</script>
{% endblock %}

If you refresh, this expression returns true. Oh JavaScript!

Be Better: use strict

Back in RepLogApp, forgetting var is actually a mistake, but JavaScript is friendly, and it allows us to make mistakes. In real life, friendly and forgiving people are great friends! In programming, friendly and forgiving languages mean more bugs!

To tell JavaScript to stop being such a pushover, at the top of the RepLogApp.js file, inside quotes, say 'use strict':

73 lines web/assets/js/RepLogApp.js
'use strict';
(function(window, $) {
... lines 4 - 57
Helper = {
... lines 59 - 70
};
})(window, jQuery);

Tip

Even better! Put 'use strict' inside the self-executing function. Adding 'use strict' applies to the function its inside of and any functions inside of that (just like creating a variable with var). If you add it outside of a function (like we did), it affects the entire file. In this case, both locations are effectively identical. But, if you use a tool that concatenates your JS files into a single file, it's safer to place 'use strict' inside the self-executing function, to ensure it doesn't affect those other concatenated files!

I know, weird. This is a special JavaScript directive that tells your browser to activate a more strict parsing mode. Now, certain things that were allowed before, will cause legit errors. And sure enough, when we refresh, we get:

Uncaught reference: Helper is not defined

Sweeeet! Even PhpStorm isn't fooled anymore, it's reporting an:

Unresolved variable or type Helper

Re-add var, and life is good!

73 lines web/assets/js/RepLogApp.js
'use strict';
(function(window, $) {
... lines 4 - 57
var Helper = {
... lines 59 - 70
};
})(window, jQuery);

Leave a comment!

  • 2017-08-25 weaverryan

    Hey Lukáš Pápay!

    This is great! I've seen this pattern... but I don't know why I didn't show it! Honestly, while it has the same effect, I like returning a var from the self-executing function better than modifying window inside, actually :).

    Anyways, thanks for sharing! Very good example!

  • 2017-08-24 Lukáš Pápay

    I think another good way how to hide private functions would be using "Revealing Module Pattern". You don't need to create any Helper object and as a bonus you get rid of binding "this" to the context. Using this approach you just return from within self-executing function only the methods you want to be public. Another advantage is you don't need to bind anything to window object. Instead you assign self-executing function to a variable.


    'use strict';

    //note: we are not binding to window object

    var RepLogApp = (function($) {
    var $wrapper = null;

    function initialize($wrapp) {
    $wrapper = $wrapp;

    $wrapper.find('.js-delete-rep-log').on(
    'click',
    handleRepLogDelete // note: no binding necessary
    );
    $wrapper.find('tbody tr').on(
    'click',
    handleRowClick
    );
    }

    function updateTotalWeightLifted() {
    $wrapper.find('.js-total-weight').html(
    // we are calling method without "this" keyword
    calculateTotalWeight()
    );
    }

    // some other methods go here ....

    function calculateTotalWeight() {
    var totalWeight = 0;
    $wrapper.find('tbody tr').each(function () {
    totalWeight += $(this).data('weight');
    });

    return totalWeight;
    }

    return {
    // methods what you return will be public
    // note: there is no "calculateTotalWeight" method listed - so it's private
    initialize: initialize,
    updateTotalWeightLifted: updateTotalWeightLifted,
    handleRepLogDelete: handleRepLogDelete,
    handleRowClick: handleRowClick
    }

    })(jQuery);

    // usage is the same

    //index.html.twig
    $(document).ready(function() {
    var $table = $('.js-rep-log-table');
    RepLogApp.initialize($table);
    });
  • 2017-06-30 Victor Bocharsky

    Hey Yan,

    Haha, we're glad you like it! Yea, JS become really fun and easy if you start understanding it better. ;)

    Cheers!

  • 2017-06-30 Yan Yong

    Now I know why we use window and $ in self executing function as argument, excellent explanation. As a PHP dev, it always makes my head spin when I working with js. My headache is getting better.

  • 2017-01-25 weaverryan

    I wasn't exactly sure myself - you made me do further research ;). We'll have a note added to text & then video in the coming days.

    Thanks for the tip!

  • 2017-01-25 Christophe Coevoet

    Ah, I was not aware that it was limited per file. But I concatenate my JS files all the time anyway, which may be why I missed this distinction.

  • 2017-01-25 weaverryan

    Yo Stof!

    Actually, we should definitely add a note about the scope of "use strict" - you're absolutely right. But, I don't believe that adding it at the top of the file affects other files (e.g. http://stackoverflow.com/qu... - each file is considered its own "program" and acts independently. I just verified this by keeping the "use strict" at the top of RepLogApp.js, and then setting a variable without "var" inside of index.html.twig. But, as the StackOverflow mentions, if you perform a simple concatenation of your JS files, then you can still have a problem. So I'm going to add a note about this!

    Thanks!

  • 2017-01-24 Christophe Coevoet

    Adding 'use strict' should be done only inside the IIFE. The strict mode directive applies to the current scope and all child scopes. By putting it outside the IIFE, it puts the global context in strict mode and so all contexts as well, which is likely to break third-party libraries not written as strict.
    By putting it inside the IIFE, only your own code (which lives inside the IIFE) will be turned into strict mode