Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

The window Object & Global Variables

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

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

(function() {
var RepLogApp = {
... lines 3 - 50
};
... lines 52 - 69
})();

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

(function() {
window.RepLogApp = {
... lines 3 - 50
};
... lines 52 - 69
})();

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

... 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:

(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 $:

(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:

... 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:

(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:

... 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':

'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!

'use strict';
(function(window, $) {
... lines 4 - 57
var Helper = {
... lines 59 - 70
};
})(window, jQuery);

Leave a comment!

14
Login or Register to join the conversation
Default user avatar
Default user avatar Lukáš Pápay | posted 5 years ago | edited

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);
});
2 Reply

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!

Reply
Default user avatar
Default user avatar Christophe Coevoet | posted 5 years ago

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

1 Reply

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!

1 Reply
Default user avatar
Default user avatar Christophe Coevoet | weaverryan | posted 5 years ago

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.

Reply

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!

Reply
Default user avatar
Default user avatar Yan Yong | posted 5 years ago

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.

Reply

Hey Yan,

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

Cheers!

Reply

Only one thing I didn't understand. You said that passing jQuery as an argument eliminates the possibility of it be set to null. But when we add the 'window' argument, we could modify it (adding the RepLogApp). Maybe because 'window' argument has the same name as global 'window', the code inside the function actually used the global variable?

Reply

Hey boykodev!

Great question - good attention to detail :). When the code inside the function references window, it's referencing the window argument that's passed into the function (at the very top). This, by chance, has the same name as the global variable, but that's not important. Let me give you an example. This code would also work:


(function(fooBarNotWindow, $) {
    fooBarNotWindow.RepLogApp = {
        // ...
    }
})(window, jQuery);

In this case, we are still ultimately modifying the global, window object - but we just gave it a different name. So.... I guess my point is this: in the original code, by passing window as an argument, it means that when we reference window in the function, it refers to the argument, not the global variable. Of course, the variable is a reference to the global object. But, there is one subtle difference: you can modify window, but you could not accidentally replace it:


// inside the self-executing function

// yep, you can modify the window object, it references the global object
window.foo = 'bar';

// but if you do this, it will NOT actually set the *global* window object to null
// this will *only* set the *local* variable to null.
window = null;

I hope that explanation wasn't too confusing :). The self-executing function gives you some isolation, and it's important to understand why they're used. But it's still a bit imperfect. The module system (as you'll see in our Webpack tutorial) is a more perfect system.

Cheers!

1 Reply

Wasn't confusing at all, thanks! What about if you still want to access a global 'window', inside the function with 'window' argument. Is there a way to do this?

Reply

Awesome :). Well, you still are referencing the global window argument. In some ways, it's no different than this in PHP:


$foo = new Foo();
$bar = $foo;

// we're referencing $bar, but obviously, we're also referencing $foo, they're the same
$bar->getName();
// but doing this does not *also* unset $foo
$bar = null;

The only thing that you absolutely will not be able to do if you have the window argument is set the global window variable to a completely different value (e.g. window = 'foo'), because this will only change the local variable. And there's no way around this (if you have the window argument), which is really the point of the self-executing function :).

Cheers!

1 Reply

Oh, I see...you can modify the object, you just can't set it to something else. It's some sort of a foolproof system :)

Reply

Exactly right :). It's similar to the const keyword you'll learn about in our ES6 tutorial: a constant can still be modified, just not re-assigned.

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony... but since it's a JavaScript tutorial, the concepts are still ? valid!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.1.*", // v3.1.10
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.1
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.4.0
        "symfony/monolog-bundle": "^2.8", // 2.12.0
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.1
        "symfony/phpunit-bridge": "^3.0" // v3.1.6
    }
}
userVoice