Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

var Versus let: Scope!

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

When you look at ES2015 code, one thing tends to jump out immediately: suddenly instead of seeing var everywhere... you see something called let! In our JS file, if you scroll down to the bottom, PhpStorm has highlighted my var with a warning:

var used instead of let or const

What's going on?

To find out, change var to let and then console.log(totalWeight):

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 176
$.extend(Helper.prototype, {
calculateTotalWeight: function() {
let totalWeight = 0;
this.$wrapper.find('tbody tr').each((index, element) => {
... line 181
... lines 184 - 185
})(window, jQuery, Routing, swal);

Now, go refresh. This function is called a bunch of times as the table is loading. And... it looks like everything works fine. It looks like var and let are equivalent?

In most cases, that's true! let is a new way to initialize a variable that's almost the same as var. 99% of the time, you can use either one, and it won't make a difference.

Understanding the Scope of var

So... what about that last 1%? Well, it has to do with variable scope. To understand, go back to play.js. At the top, add something silly: if (true) then aGreatNumber = 42, which of course, is a great number:

12 lines play.js
var aGreatNumber = 10;
if (true) {
aGreatNumber = 42;
... lines 6 - 12

When we run it, it re-assigns the variable to 42. No surprises. But what if we added var aGreatNumber = 42?

12 lines play.js
var aGreatNumber = 10;
if (true) {
var aGreatNumber = 42;
... lines 6 - 12

I shouldn't need to say var again: the variable has already been initialized. But, will this give us an error? Or change anything?

Let's find out! No! We still see 42. When we use the second var, it re-declares a new variable called aGreatNumber. But that doesn't make any real difference: down below, it prints the new variable's value: 42.

But now, wrap this same code in a self-executing function. Use the new arrow syntax to be trendy, then execute it immediately:

14 lines play.js
var aGreatNumber = 10;
if (true) {
(() => {
var aGreatNumber = 42;
... lines 8 - 14

Will this change anything? Try it!

Woh! It prints as 10! Why!?

Remember, the scope of a variable created with var is whatever function it is inside of. Let's follow the code. First, we create the variable and set it to 10. Then we create a new variable set to 42. But since this is inside of a function, its scope is only this function. In other words, inside of the self-executing block, aGreatNumber is 42. But outside, the original variable still exists, and it's still set to 10. Since we're printing it from outside the function, we see 10.

Okay okay, I know, this can be confusing. And most of the time... this subtle scope stuff doesn't make any difference. But, this is exactly where var and let different.

The "Block" Scope of let

Let me show you. Remove the self-executing function and change each var to let:

12 lines play.js
let aGreatNumber = 10;
if (true) {
let aGreatNumber = 42;
... lines 6 - 12

If let and var behaved exactly the same, we would expect this - just like before - to print 42. Try it.

But no! It prints 10! And this is the difference between var and let. With var - just like with any variable in PHP - a variable's scope is the function it's inside of, plus any embedded functions. But let is different: it's said to be "block-scoped". That means that anytime you have a new open curly brace ({) - like an if statement or for loop - you've entered a new scope for let. In this case, let is equal to 42, only inside of the if statement. Outside, it's a completely different variable, which is set to 10.

Of course, if we remove the extra let statement and try it, now we get 42:

12 lines play.js
let aGreatNumber = 10;
if (true) {
aGreatNumber = 42;
... lines 6 - 12

This is because without the let, we're no longer creating a new variable: we're simply changing the existing variable to 42.

If this makes your head spin, me too! In practice, there are very few situations where var and let behave different. So, use your favorite. But there is one other tiny thing that makes me like let, and it deals with variable hoisting.

Leave a comment!

Login or Register to join the conversation
Cat in space

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

This tutorial uses Symfony 3. But, since this is a JavaScript tutorial, all the concepts work fine in newer versions of Symfony.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.2.*", // v3.2.14
        "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.2
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.19
        "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.2
        "symfony/phpunit-bridge": "^3.0" // v3.2.2