Tools Blog Learn Quizzes Smile API Log In / Sign Up
Tools Blog Learn Quizzes Smile API Log In / Sign Up
« Return to the tutorials list
We have updated the website and our policies to make sure your privacy rights and security are respected.
Click here to learn more about the way our website handles your data.

Remove this message.

The basics of variable scope in JavaScript

Difficulty: 35 / 50 Tweet
scope

Update: May 7 2018 - This article was initially written in early 2015 when ES6 was just a fairytale. Now that ECMAScript 2015 is widely used, I thought it would be a good idea to update the article with the changes that ES6 introduced in terms of variable scope.

In addition to var ES6 introduced let and const constructs that create block-scoped variables - variables that exist only in the block of code that encloses their definition.

When running the example below, you'll get an error because a is not defined in the global scope - it only exists in the block between the curly braces.

    {
        let a = 2;
        console.log(a);
    } 
    console.log(a);

In this example, the logged value will be 1 because let a=2; is only available inside the if-block and it is a different variable than the one defined in the first row of the function. Of course, if you want a from within the if statement to point to the original, you just need to remove the let construct.

    function x() {
        let a = 1;
        if(1==1) {
            let a = 2;
        }
        console.log(a)
    }

One more thing to consider, when it comes to variables declared with let and const is that for these there is no hoisting. In other words, as opposed to var declarations, this will throw an error:

    {
        console.log(r);
        let r = 1;
    }

Also, global variables declared with let and const are not automatically transformed into properties of the global object (window in the case of the browser):

    let r = 1;
    var v = 1;
    console.log(window.r) //error
    console.log(window.v) //1

In this post, I will try to explain JavaScript variable scopes and hoisting as well as the many implicit gotchas. To make sure we don't bump into any sudden, unforeseen problems while coding, we must really understand these concepts. Otherwise the trivial task of reading more advanced JavaScript code can prove an overwhelming and most times an unsuccessful attempt.

Below is the original article, written in 2015 which refers only to ES5-style variables.

The scope is the "bucket" in which a variable exists and it is useful because it catalogs the "buckets" from which you can access a variable and if you actually have access to a variable from within a particular "bucket". Variables can be local or global, but the way variables that are defined in a parent scope can be accessed from a child "bucket" can be a little tricky.

Declaring a variable in JavaScript is done using the 'var' keyword. Once declared the respective variable becomes a part of it's parent scope. When defining a variable inside the global scope it is available in local scopes, but a locally defined variable is not available in the global one.

Let's have a look at an example. Executing the code below you will notice that the log inside the function will output the value of the globally defined variable, while the log of the locally defined variable from the global scope will throw an error because the variable is not a part of the global scope and therefor seen as undefined.

    
      <script>
        var agloballydefinedvariable = 'Global';
        function someFunction() {
          var alocallydefinedvariable = 'Local';
          console.log(agloballydefinedvariable); // Global
        }
        console.log(alocallydefinedvariable); // Uncaught ReferenceError: alocallydefinedvariable is not defined
      </script>
    
  

The Scope Chain

Things can get really bad if you forget to define a local variable using the "var" keyword. The reason why this can happen is because JavaScript will automatically consider that an undefined variable should be searched for in parent scopes leading up to the global one. In the example below, JavaScript will know that the "a" variable inside someFunction() is local, but in the anotherFunction() it will look for its assignment in parent scopes and it will find it in the parent function.

  
    <script>
      var a = 1;
      function someFunction() {
        var a = 2;
        function anotherFunction() {
          console.log(a); // the output will be 2
        }
      }
    </script>
  

To complicate things further, considering the example below, the a variable is not defined in any of the function scopes.

  • When someFunction() is called a is not the defined in the function scope so the a=2; assignment will overwrite the value of the global a variable.
  • Subsequent references to a will point to the value of the globally defined variable.

  
    <script>
        var a = 1;
        function someFunction() {
          a = 2;
          function anotherFunction() {
            console.log(a); // the output will be 2
          }
          anotherFunction();
        }
        someFunction();
        console.log(a); //the output will be 2
    </script>
  

Hoisting

Hoisting is the process through which variable declarations are automatically "raised" at the top of a function or the global context (if outside a function). Keep in mind that only variable declarations are hoisted, not variable initialization or assignments so, in the case of the code below, the first output will be undefined... but it will not throw out any erros.

  
    <script>
      console.log(a); //undefined
      var a = 1;
      console.log(a); //1
    </script>
  

In other words, the example above, can be translated as this:

  
    <script>
      var a;
      console.log(a); //undefined
      a = 1;
    </script>
  

In the Window

In browser-based JavaScript, variables defined as a part of the global scope are actually properties of an object called 'window'. Window is the 'container' I was referring to when talking about scopes. This being said, when you want to modify a globally defined variable from a local scope, you can also do that by modifying the respective property of the window object.

  
    <script>
      var myVariable = 'Global Scope';
      function myFunction() {
        window.myVariable = 'Something Else';
      }
      myFunction();
      console.log(myVariable); // Something Else
    </script>
  

In the above code the myVariable window property is modified from within a local scope ('myFunction'), by altering the value of the 'myVariable' window property.

A few more examples of possible gotchas

If inside a function you assign a value to a variable that hasn't been previously defined, it automatically becomes a part of the global scope.

  
    <script>
      function myFunction() {
        myVariable = 'JavaScript';
      }
      myFunction();
      console.log(myVariable); //JavaScript
    </script>
  

If you're not careful and forget to define a local variable, it might throw off your entire script.

  
    var city="LA";
    var team="Lakers";

    function showTeam () {
        console.log (city + " " + team);
    }

    function showCity () {
        city = "Moscow";
        console.log (city);
    }

    showTeam(); // LA Lakers
    showCity(); // Moscow
    /*
    Because in showCity (above) 
    the variable "city" is not defined using "var"
    city references the global variable and overwrites it
    leading to the problem below :)
     */
    showTeam(); // Moscow Lakers
  

An inner function will store a local variable even after the outer function has returned.

This may sound a little weird at first, but once we'll go through it you'll understand it better. The best way to explain this is to use a simple 'Hello World' example.

  
    function greet(who) {
        var iterations = 0;
        return function () {
            console.log(++iterations);
            return 'Hello ' + who + '!';
        };
    }

    var greeting = greet('World');
    console.log(typeof greeting); //function
    console.log(typeof greeting()); //string & iterations=1
    console.log(greeting()); //Hello World! & iterations=2
    console.log(greeting("Universe")); //Hello World! & iterations=3
  

As you can see from the above, greet() returns an inner function which is called a "closure". Among other things, closures store a reference to variables and parameters from the enclosing function inside their own local scopes. Referring to our specific example, the parameters who and iterations become local variables in the closure.

This means, that greeting has become a function (the anonymous function returned by greet) and that who and iterations are local variables inside the inner function.

Subsequent calls to greeting() will not invoke greet() again - instead, they will only execute the closure and always return "Hello World!", while incrementing the local iterations variable (not the one defined in the scope of greet(who)).

I have also made a short video tutorial that hopefully explains this better. Have a look!

comments powered by Disqus