Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

JavaScript variable scoping and its pitfalls

DZone's Guide to

JavaScript variable scoping and its pitfalls

· Web Dev Zone
Free Resource

Start coding today to experience the powerful engine that drives data application’s development, brought to you in partnership with Qlik.

This blog post explains how variables are scoped in JavaScript. It points out two things that can cause problems if you are not aware of them: JavaScript variables are function-scoped and they can be captured in closures.

The scope of a variable defines where the variable is accessible. For example, if a variable is declared at the beginning of a function, it is accessible from within that function, but not from outside and usually dies when the function is finished. In this case, the function is the scope of the variable. When a scope is entered, a new environment is created that maps variable names to values. Scopes can be nested. A variable is accessible in its scope and in all scopes nested within that scope.

Pitfall 1: Variables are function-scoped. Most mainstream languages are block-scoped – new environments are created when entering a block and scopes are nested by nesting blocks. In contrast, JavaScript variables are function-scoped – new environments are only created when entering a function and scopes are nested by nesting functions. That means that even if you declare a variable inside a block such as the “then” block of an if statement, it is accessible everywhere in the surrounding function. The following code illustrates this.

    var myvar = "global";
    function f() {
        print(myvar); // (*)
        if (true) {
            var myvar = "local"; // (**)
        }
        print(myvar);
    }
    > f()
    undefined
    local
    > myvar
    global
As you can see, even the first access of myvar refers to the local value of it (which is not yet assigned at (*)). The reason for this is that var-declared variables are hoisted in JavaScript: var myvar = "local" is equivalent to declaring myvar at the beginning of f() and performing a simple assignment at location (**). Thus, it is a best practice in JavaScript to only use var at the beginning of a function.

Pitfall 2: Closures. Scoping in JavaScript is static, it is determined by the nesting of syntactic constructs. [As an aside, JavaScript’s with statement supports dynamic scoping. But that statement will probably be removed from the language in the long term.] To ensure static scoping, the environment is attached to values that access variables in the environment. An example of such a value is the returned function in the following code.

    function f() {
        var x = "abc";
        return function() {
            return x;
        }
    }
Interaction:
    
    > var g = f();
    > g()
    abc
Variable x is free within the returned function, it cannot be resolved within it. By attaching the environment, x can be resolved to the value we expect given static scoping. This pairing of a value with an environment is called a closure, because the free variables are closed over. JavaScript’s closures are very powerful. You can even use them to store the properties of an object, as demonstrated in the following code.
    function objMaker(color) {
        return {
            getColor: function() {
                return color;
            },
            setColor: function(c) {
                color = c;
            }
        };
    }
Interaction:
    > var c = objMaker("blue");
    > c.getColor()
    blue
    > c.setColor("green");
    > c.getColor()       
    green
The value of color is stored in the environment that has been created when invoking objMaker(). That environment has been attached to the returned value, which is why color is still accessible even after objMaker() has terminated. Closures plus function-scoped variables lead to unexpected behavior. Given the following code.
    function f() {
        var arr = [ "red", "green", "blue" ];
        var result = [];
        for(var i=0; i<arr.length-1; i++) {
            var func = function() {
                return arr[i];
            };
            result.push(func);
        }
        return result;
    }
This function returns an array with two functions in it. Both of these functions can still access f’s environment and thus arr. In fact, they share the same environment. But in that environment, i has the value 2 and thus both functions return "blue" when invoked:
    > f()[0]()
    blue
This is not what we wanted. In order for this to work, we need to make a snapshot of the index i before creating a function that uses it. In block-scoped programming languages the following would work.
    function f() {
        var arr = [ "red", "green", "blue" ];
        var result = [];
        for(var i=0; i<arr.length-1; i++) {
            var j=i; // fresh copy for func? Not in JavaScript!
            var func = function() {
                return arr[j];
            };
            result.push(func);
        }
        return result;
    }
JavaScript is function-scoped, so j is handled as if it had been declared at the beginning of f() and we don’t get a different environment for each func that we return. To do that, we need to use a function.
    function f() {
        var arr = [ "red", "green", "blue" ];
        var result = [];
        for(var i=0; i<arr.length-1; i++) {
            var func = function() {
                var j=i; // fresh copy
                return function() {
                    return arr[j];
                }
            }();
            result.push(func);
        }
        return result;
    }
We wrapped another function around the actual func and immediately invoked it. Thus, a completely new environment is created for each iteration of the loop. Now the result is as expected:
    > f()[0]()
    red
Defining a function and immediately invoking it is a common pattern in JavaScript, because it allows one to create new environments. You can also make the color a parameter of the environment-creating function.
    function f() {
        var arr = [ "red", "green", "blue" ];
        var result = [];
        for(var i=0; i<arr.length-1; i++) {
            var func = function(color) {
                return function() {
                    return color;
                }
            }(arr[i]);
            result.push(func);
        }
        return result;
    }
Note that the example does have real-world relevance, because similar scenarios arise when adding handlers to DOM elements.

 

From http://www.2ality.com/2011/02/javascript-variable-scoping-and-its.html

Create data driven applications in Qlik’s free and easy to use coding environment, brought to you in partnership with Qlik.

Topics:

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}