If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeNow 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!
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.
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.
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
.
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!
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'
:
; | |
(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!
; | |
(function(window, $) { | |
... lines 4 - 57 | |
var Helper = { | |
... lines 59 - 70 | |
}; | |
})(window, jQuery); |
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!
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
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!
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.
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!
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.
Hey Yan,
Haha, we're glad you like it! Yea, JS become really fun and easy if you start understanding it better. ;)
Cheers!
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?
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!
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?
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!
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 :)
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.
// 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
}
}
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.