Skip to main content
Engineering LibreTexts

5.5: Scoping in JavaScript

  • Page ID
    27563
  • Variables in JavaScript can have 3 types of scoping; block, function (or local), and global. These 3 types of scoping will be explained briefly here, mainly in preparation for the next section on closure. While it seems like having only 3 types of scope would make the concept of scope easy to understand, it always seems to be one of the most difficult concepts to get across to programmers.

    There are some parts of ECMA6+ that are very useful, and one of them is the inclusion of the let keyword. The let keyword has allowed block scoping in JavaScript but has muddied the waters and caused additional difficulties in programs that combine the use of the let and the older var keyword. This text will avoid those problems by always using the let keyword.

    While a JavaScript programmer should probably understand the var keyword, the only reason is to understand how it impacts older programs that use it. When it is used, it can cause some variables that would be block scoped using the let keyword to be function scoped. If a reader gets into a situation where they are supporting code that uses the var keyword, there are any number of good sites that explain what it means. This will not be a problem for readers who have understood the 3 types of scoping described here.

    5.4.1 Undeclared variables

    First, JavaScript does not require that variables be declared. Variables that are not declared are created and put in the global scope. This allows for all sorts of problems, such as misspelling of variables, or variables meant to be in other scopes being put in global scope.

    To avoid accidently spelling variables wrong or to prevent programmers from using undeclared variables, the JavaScript program can use the “use strict” directive in their programs. This will cause the program to fail with error if an undeclared variable is encountered. For this book, the use strict directive will be used, and all variables must be declared. Undeclared variables simply should not exist. The issue of undeclared variables is therefore dispensed with, and not covered further.

    5.4.2 The let keyword

    The let keyword declares a variable, and scopes that variable to the next larger program block, where the next larger program block is a unit of a program that is contained between two curly braces ({}). A variable declared inside a block will be scoped to the block represented by those curly braces. If the variable is outside of any curly braces, it will have global scope.

    The first type of scoping that will be covered is block scoping. Block scoping allows a variable to exist only in the block in which it is declared. This is often useful for variables that should only exist in a block, for example a for loop. The following for loop shows a good use of the let variable for block scoping:

    function myFunc() {
        for (let count = 0; count < 10; count++) {
            Do something
        }
    }
    

    Here the next larger program block is the for loop, so the variable count will only exist in the for loop and cannot be used outside of that loop. The variable will come into existence when the loop block is entered and will be destroyed when the loop is exited. This use of scoping is what most programmers are used to seeing.

    The next type of scoping is function scope, which is also called local scope in JavaScript. Function scope occurs when a variable is declared inside of a program block that is a function. The following program shows the loop above, but now the variable count has function scope.

    function func() {
        let count = 0;
        for (count = 0; count < 10; count++) {
            Do something
        }
    }                 
    

    The difference between the block scope and function scope code would be basically meaningless in most programming languages such as C#/Java/C++ because function variables only exist while the function exists (e.g. the function is on the program stack). In Java/C#/C++, the function is pushed on the stack when the function is entered and popped when it is exited.

    Function scope in JavaScript is very different, and function variables are not released when a function exits. This will be covered in more detail in the section on Closure.

    The final type of scoping in JavaScript is global scoping. A variable declared outside of any block of code defined by curly braces ({}) is globally scoped. There is one copy of a global variable in the entire program, and for the purposes of this discussion, that copy comes into existence when the program starts running and exists the entire time the program runs. This is similar to a Java/C# static variable. The following shows how the loop above would look if a global variable was used. Note that in this case, the variable must be reset each time the function is entered, as the previous value from the loop is still stored in the variable.

    let count = 0;
    function func() {
        count = 0;
        for (count = 0; count < 10; count++) {
            Do something
        }
    }
    

    The take away from this section is that scoping controls where a variable is accessible and the time the variable definition is maintained. Block variables and function variables are only visible in the block/function they are declared in. Block variables only exist so long as the program is executing in the block they are declared in. Function variables exist for a much longer period, but that will be covered in the section on closures. Global variables are always accessible, and always exist.

    The only strange idea coming from this section is how long function variables live, as unlike other languages, they do not go away when the function exits. This is the subject of the next section on closures.

    5.3.1 Closures

    One interesting aspect of JavaScript that is not found in most languages the reader will be familiar with (e.g. Java, C++, C#, etcetera) is that a function (an outer function) can contain other functions (inner functions). This program structure gives rise to an interesting problem with variables known as closure. The problem starts with the fact that variables scoped in the outer function are also scoped in the inner functions, as shown in Program 96 below.

    Program 102 - Calling an inner function from an outer function.
    
    <script>
        function f1() {
            let var1 = "function scope"
                
                function f2() {
                    console.log(var1);
                }
                
                f2();
        }
        
        f1();
    </script>                            
    

    This example by itself seems very natural and does not seem to be a problem. The issue arises when the inner function is used as a lambda value. The lambda function can now be called after the outer function has completed, causing the variable scoped in the outer function to be referenced after the outer function has completed running. This is shown in the following program.

    Program 103 - Running an inner function after an outer function has completed.
    
    <script>
        let obj1 = new function f1() {    
            console.log("Entering f1");
            let var1 = "function scope"
            
            this.f2 = function() {
                console.log(var1);
            }
            console.log("Leaving f1");
        }
        
        obj1.f2();
    </script>                        
    

    This program illustrates the closure problem. The inner function, f2, is associated as a lambda value property in the obj1 object. This lambda function exists even after the function f1 has completed and exited. In Java/C#/C++/etcetera, the variables of the outer function are allocated on the program stack and cease to exist after the outer function has completed running and so in Java the variable var1 would not be available when f2 is called using the reference in obj1. The nature of JavaScript requires, however, that the function local variables outlive the execution of the function, just for this type of situation where inner functions can exist (as lambda values) after the function has executed. This is a JavaScript closure. Closure is not hard because it is a difficult concept, instead it is hard because programmers generally have not seen it before, and it is often explained in terms of its impacts, and how they are different from Procedural and class- based OOP languages.

    The concept of a closure is simple, but the implications are huge, and the concepts are not easily understood in terms of class-based OOP or procedural languages38. The need to make function variables outlive the function execution is the crux of the reason why closures are included in JavaScript. JavaScript Closures are really not that complicated, especially when used for their purpose. Once again, problems arise when trying to use an invalid metaphor to understand a concept, resulting in the myriad of helpful web pages that attempt to ferret out how to understand a closure using the wrong metaphor.

    This concept of closure will be looked at in more detail later in the chapter when an encapsulated object model in JavaScript is describe.


    38 There are analogs to this behavior of requiring a method local variable to outlive the method it is declared C# and Java. Closure analogs provide much of the justification for anonymous inner classes in Java, final local variables when using Java Events, and the new inclusion of lambda functions. The basic problem is the same in all of these languages, however Java used anonymous inner classes, and the syntactic sugar of Java lambda functions, to handle these problems. Understanding why the problems occur in JavaScript helps understand why they occur in Java, but the solutions are completely different.

    • Was this article helpful?