Demystifying JavaScript Variable Scope and Hoisting

article

#1

Originally published at: http://www.sitepoint.com/demystifying-javascript-variable-scope-hoisting/

Every building needs strong foundation to build on. Understanding variable scope in JavaScript is one of the keys to building a solid foundation. This article will explain how JavaScript’s scoping system works. We’ll also cover a related topic known as hoisting.


Variable Scope


To work with JavaScript efficiently, one of the first things you need to understand is the concept of variable scope. The scope of a variable is controlled by the location of the variable declaration, and defines the part of the program where a particular variable is accessible.


Scoping rules vary from language to language. JavaScript has two scopes – global and local. Any variable declared outside of a function belongs to the global scope, and is therefore accessible from anywhere in your code. Each function has its own scope, and any variable declared within that function is only accessible from that function and any nested functions. Because local scope in JavaScript is created by functions, it’s also called function scope. When we put a function inside another function, then we create nested scope.


Currently, JavaScript, unlike many other languages, does not support block level scoping. This means that declaring a variable inside of a block structure like a for loop, does not restrict that variable to the loop. Instead, the variable will be accessible from the entire function. It’s worth noting that the upcoming ECMAScript 6 will support block level scopes via the let keyword.


To make things clear let’s use a simple metaphor. Every country in our world has frontiers. Everything inside these frontiers belongs to the country’s scope. In every country there are many cities, and each one of them has its own city’s scope. The countries and cities are just like JavaScript functions – they have their local scopes. The same is true for the continents. Although they are huge in size they also can be defined as locales. On the other hand, the world’s oceans can’t be defined as having local scope, because it actually wraps all local objects – continents, countries, and cities – and thus, its scope is defined as global. Let’s visualize this in the next example:


  
var locales = {
  europe: function() {          // The Europe continent's local scope
    var myFriend = "Monique";

    var france = function() {   // The France country's local scope
      var paris = function() {  // The Paris city's local scope
        console.log(myFriend);
      };

      paris();
    };

    france();
  }
};

locales.europe();

Try out the example in JS Bin


Now that we understand what local and global scopes are, and how they are created, it’s time to learn how the JavaScript interpreter uses them to find a particular variable.


Back to the given metaphor, let’s say I want to find a friend of mine whose name is Monique. I know that she lives in Paris, so I start my searching from there. When I can’t find her in Paris I go one level up and expand my searching in all of France. But again, she is not there. Next, I expand my searching again by going another level up. Finally, I found her in Italy, which in our case is the local scope of Europe.


In the previous example my friend Monique is represented by the variable myFriend. In the last line we call the europe() function, which calls france(), and finally when the paris() function is called, the searching begins. The JavaScript interpreter works from the currently executing scope and works it way out until it finds the variable in question. If the variable is not found in any scope, then an exception is thrown.


This type of look up is called lexical (static) scope. The static structure of a program determines the variable scope. The scope of a variable is defined by its location within the source code, and nested functions have access to variables declared in their outer scope. No matter where a function is called from, or even how it’s called, its lexical scope depends only by where the function was declared.


In JavaScript, variables with the same name can be specified at multiple layers of nested scope. In such case local variables gain priority over global variables. If you declare a local variable and a global variable with the same name, the local variable will take precedence when you use it inside a function. This type of behavior is called shadowing. Simply put, the inner variable shadows the outer.


That’s the exact mechanism used when a JavaScript interpreter is trying to find a particular variable. It starts at the innermost scope being executed at the time, and continue until the first match is found, no matter whether there are other variables with the same name in the outer levels or not. Let’s see an example:


Read more on SitePoint…


#2

That statement is untrue. All modern browsers support block level scope in JavaScript. The most modern browser to not support block level scope in JavaScript is IE10.


#3

let doesn't work for me in either the latest Chome or Firefox. confused


#4

In Chrome type chrome://flags/ into your omnibar and enable Experimental JavaScript, that should enable support for ES6 features.


#5

Ahh, gotcha. Though, I don't think that's enough to say that Chrome supports "let". If it's experimental and off by default, then I think the article author called it right.


#6

Agreed, based on my tests IE 12 will be the first browser to natively support 90% or more of the ES6 features upon it's arrival next year.


#7

I agree. Obviously the place I looked up the information about support for it was incorrect regarding support in Chrome.

It has been supported if Firefox for a long time (since that was the browser that originated the idea long before it got added into JavaScript) and is now also in IE so it is still true to say that most popular modern browsers support it so saying that it isn't supported in JavaScript isn't correct.

It is probably more accurate to say that it is in the process of being added into JavaScript but that not all browsers support it yet.


#8

I just tried it again, but it still isn't working in Firefox.

Here's the test I've been running (taken from MDN page):

var a = 5;
var b = 10;

if (a === 5) {
  let a = 4; // The scope is inside the if-block
  var b = 1;

  console.log(a);
  console.log(b);
}

console.log(a);
console.log(b);

But all I get in Firefox is SyntaxError: missing ; before statement. If I change "let" to "var", then the error goes away, which means "let" is the culprit.

EDIT: There's a footnote on the MDN page that says:

Only available to code blocks in HTML wrapped in a <script type="application/javascript;version=1.7"> block

When I used this, then Firefox successfully ran "let". However, this type attribute also caused Chrome to not execute the code within that <script> block at all, which in my book makes this a no-go.


#9
 var test = "I'm global";

function testScope() {
  test = "I'm local";

  console.log(test);     
}

console.log(test);     // output: I'm global

testScope();           // output: I'm local

console.log(test);     // output: I'm local (the global variable is reassigned)

This time the local variable test overwrites the global variable with the same name. When we run the code inside testScope() function the global variable is reassigned. If a local variable is assigned without first being declared with the var keyword, it becomes a global variable. To avoid such unwanted behavior you should always declare your local variables before you use them. Any variable declared with the var keyword inside of a function is a local variable. It’s considered best practice to declare your variables.

I think that is untrue that you declare a new local variable within function scope without var statement. In this particular case you just reassign a global variable test.

All variables that can be used from the outside of scope is a global variable and all variables which is not declared with var statement is a global variable no matter that is within scope or not.

If you declare or assign a global variable within function scope without var statement it can only be used after the variable is declared / assigned from inside the function and it also can be used from outside the function scope after the function has been invoked like a common global variable that declared from the outside of function scope.

a = 0; // global variable
// similar with this
var a = 0; //global variable

var b; // declare a global variable
// b has no value yet
function globalFunc() {
    b = 0; // assign a global variable
    // now b has value 0 and ready to be used
}
// b has no value yet
globalFunc();
// now b has value 0 and ready to be used

function hasLocalVar() {
    var c = 0; // declare and assign a new local variable
    // c can be used here
}
// no variable c is found here
hasLocalVar();
// no variable c is found here

#10

Looks like node doesn't support "let" without special flags, either. Seems that almost all current javascript implementations have experimental support at best.


#11

That gives them about three months to fix it given that it will officially become a part of JavaScript in March next year.


#12

"In the code above we saw that the function declaration takes precedence over the variable declaration."

This does not appear to be reliable behavior. For example, running the JS Bin succeeds as expected on Chrome, but on Firefox 34.0 on OS X it produces 'TypeError: showState is not a function'.


#13

I tested the code example on different browsers and it works. It seems that the issue happens only on Firefox. So, my words quoted by you are correct. The behavior of JavaScript is reliable, but we can't say the same about its browsers' implementations.


#14

There is no reason for using function declarations in JavaScript. They don't always work where as the alternative of creating an anonymous function and assigning it to a variable always works.


#15

I can't say I never use function declarations, but at this point my general strategy is to use named function expressions. It can feel a bit redundant, but I like that it allows me to have a clear list of the identifiers at play AND to avoid the ambiguity of anonymous functions in stack traces. Of course this also creates issues about which one must remain cognizant: JScript bugs etcetera

Personally, I'm not aware of any perfect solution at this point, although browsers are, on the whole, getting better at associating names with anonymous functions in traces. In any case, though, I've yet to run into a circumstance wherein I'm forced to deal with anything quite like the contrived example about which I first commented.

I'm happy to hear your thoughts on the topic if you have more, though.


#16

It's worth making the point that you shouldn't rely on hoisting behaviour in your coding style. It's worth understanding it, but actually scripting in a way that relies on it is a recipe for much confusion. A call to a function should always come after its declaration.


#17

The best alternative is to write the code so that the declarations appear at the spot in the code that they will be run so that you don't need to remember that the declaration gets hoisted because you have written it in that location to start with.


#18

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.