
Scope is an important, yet ambiguous concept in JavaScript. Used correctly, it allows you to leverage good design patterns and helps you avoid bad side effects. In this article, we will dissect the different types of scope in JavaScript and how they work deep down in order to write better code.
The simple definition of scope iswhere the compiler looks for variables and functions when it needs them. Sounds too easy to be true? Let's see what it's all about.
Before explaining what scope is, we need to talk about the JavaScript interpreter and how it affects the different scopes. When you execute your JavaScript code, the interpreter goes through the code twice.
For a more detailed look at hoisting, check myprevious article
The first run through the code - also referred to as the compile run - is what affects scope the most. The interpreter looks through the code for variable and function declarations, and moves those to the top of thecurrent scope. It's important to note thatonly declarations are moved and that assignments are left as-is for the second run - also known as the execution run.
To better understand this, let's use this simple snippet of code:
'use strict'var foo = 'foo';var wow = 'wow';function bar (wow) { var pow = 'pow'; console.log(foo); // 'foo' console.log(wow); // 'zoom'}bar('zoom');console.log(pow); // ReferenceError: pow is not definedThe above code will look like this after the compile run:
'use strict'// Variables are hoisted at the top of the current scopevar foo;var wow;// Function declarations are hoisted as-is at the top of the current scopefunction bar (wow) { var pow; pow = 'pow'; console.log(foo); console.log(wow);}foo = 'foo';wow = 'wow';bar('zoom');console.log(pow); // ReferenceError: pow is not definedThe most important thing to understand here is that declarations are hoisted to the top of theircurrent scope. This will be crucial in understanding scope in JavaScript, as we'll explain later in this article.
For instance, the variablepow was declared in the functionbar because this is its scope, instead of being declared in the parent scope.
The functionbar's parameterwow is also declared in the function scope. In fact, all function parameters areimplicitly declared within the function scope, and this is whyconsole.log(wow) on line 9 outputszoom instead ofwow.
Now that we've covered how the JavaScript interpreter works and made a brief introduction to hoisting, we can dig deeper into what scope is. Let's start with the lexical scope, which means compile-time scope. In other words,the decision for what the scope is was actually made during compilation time. For the purpose of this article, we'll ignore exceptions to this rule that occur specifically if the code useseval orwith, because we should not be using those in any case.
The interpreter's second run is where the variable assignments are made and functions are executed. In the sample code above, this is wherebar() is executed on line 12. The interpreter needs to find the declaration ofbar before executing it, and it does so by first looking in its current scope. At that point, the current scope is theglobal scope. Thanks to the first run, we know thatbar is declared at the top of the file so the interpreter can find it and execute it.
If we look at line 8console.log(foo);, the interpreter needs to find the declaration offoo before executing this line. The first thing it does, again, is look in its current scope which is the functionbar's scope this time - not theglobal scope. Isfoo declared in this function's scope? No. Then, it'll go up a level to its parent scope and look for the declaration there. The function's parent scope is theglobal scope. Isfoo declared in theglobal scope? Yes, so the interpreter can execute it.
To summarize, the lexical scope means that the scope was determined after the first run, and when the interpreter needs to find a variable or function declaration, it'll first look in its current scope but will keep going up to the parent scope as long as it doesn't find the declaration it needs. The highest level it can go up to is theglobal scope.
If it doesn't find the declaration in theglobal scope, it'll throw aReferenceError error.
Also, since the interpreter always looks for a declaration in the current scope before looking in the parent scopes, the lexical scope introduces the concept of variable shadowing in JavaScript. This means that a variablefoo declared in the currentfunction scope willshadow - or hide - a variable with the same name in the parent scope. Let's look at the following code to better understand what shadowing is:
'use strict'var foo = 'foo';function bar () { var foo = 'bar'; console.log(foo);}bar();The output of the code above isbar instead offoo because the variable declaration offoo on line 6 shadows the variable declaration offoo on line 3.
Shadowing is a design pattern that can be useful if we want to mask certain variables and prevent them from being accessed in specific scopes. That said, I personally tend to avoid using it unless absolutely necessary because I believe that using the same variable names creates more confusion among teams and can sometimes cause developers to assume the variable has a different value than what it really has.
As we saw in the lexical scope, the interpreter declares a variable in its current scope, which means that a variable declared in a function is declared in thefunction scope. This scope is limited to the function itself and its children - other functions declared within this function.
Variables declared in a function scope cannot be accessed from the outside. This is a very powerful pattern to leverage when you want to create private properties and only have access to them from within afunction scope, as we can see in the following code:
'use strict'function convert (amount) { var _conversionRate = 2; // Only accessible in this function scope return amount * _conversionRate;}console.log(convert(5));console.log(_conversionRate); // ReferenceError: _conversionRate is not definedAblock scope is similar to afunction scope, but is limited to ablock instead of a function.
As of ES3, acatch clause intry / catch statements has ablock scope, which means that it has its own scope. It's important to note that thetry clause does not have ablock scope, only thecatch clause does. Let's look at a code snippet to better understand this:
'use strict'try { var foo = 'foo'; console.log(bar);}catch (err) { console.log('In catch block'); console.log(err);}console.log(foo);console.log(err);The previous code will throw an error on line 5 when we try to accessbar, which will cause the interpreter to go into thecatch clause. This will declare anerr variable in its scope, which will not be accessible from the outside. In fact, an error will be thrown when we try to log the value oferr on the last line:console.log(err);. The exact output of this code is:
In catch blockReferenceError: bar is not defined (...Error stack here...)fooReferenceError: err is not defined (...Error stack here...)Notice how
foois accessible outside thetry / catch buterris not.
As of ES6,let andconst variables are attached implicitly to the currentblock scope instead of thefunction scope. This means that these variables are limited to the block they were declared in, whether it's anif block, afor block, or a function. Here's an example to better demonstrate this:
'use strict'let condition = true;function bar () { if (condition) { var firstName = 'John'; // Accessible in the whole function let lastName = 'Doe'; // Accessible in the `if` only const fullName = firstName + ' ' + lastName; // Accessible in the `if` only } console.log(firstName); // John console.log(lastName); // ReferenceError console.log(fullName); // ReferenceError}bar();let andconst variables allow us to use theprinciple of least disclosure, which means that a variable should only be accessible in the smallest scope possible. Before ES6, developers often used to do this stylistically by declaringvars in IIFEs, but now we can functionally enforce this in ES6 throughlet andconst. Some of the main benefits of this principle is to avoid bad access to variables and therefore reduce the possibility of bugs, and also to allow the garbage collector to clean these unused variables once we're out of theblock scope.
An Immediately Invoked Function Expression (IIFE) is a very popular JavaScript pattern that allows a function to create a newblock scope. IIFEs are simplyfunction expressions that we invoke as soon as the interpreter runs through the function. Here's an example of an IIFE:
'use strict'var foo = 'foo';(function bar () { console.log('in function bar');})()console.log(foo);This code will outputin function bar beforefoo because the functionbar is immediately executed, without having to explicitly call it throughbar(). The reasons for this are:
( before the keywordfunction, which makes it afunction expression instead of afunction declaration.() at the end, which execute thefunction expression immediately.As we saw earlier, this allows to hide variables from outer scopes, to limit their access, and to not pollute the outer scopes with unneeded variables.
IIFEs are also very useful if you are running asynchronous operations and want to conserve the state of your variables in the IIFE's scope. Here's an example of what this means:
'use strict'for (var i = 0; i < 5; i++) { setTimeout(function () { console.log('index: ' + i); }, 1000);}Despite our first assumption that this will output0, 1, 2, 3, 4, the actual result of thisfor loop that runs an asynchronous operation (setTimeout) is:
index: 5index: 5index: 5index: 5index: 5The reason for this is that by the time the 1000 milliseconds expire, thefor loop has completed and the value ofi is actually 5.
Instead, if we want to output the values0, 1, 2, 3, 4, we need to use IIFEs to conserve the scope we want, as follow:
'use strict'for (var i = 0; i < 5; i++) { (function logIndex(index) { setTimeout(function () { console.log('index: ' + index); }, 1000); })(i)}In this sample, we are passing the value ofi to the IIFE, which will have its own scope and will not be affected by thefor loop anymore. The output of this code is:
index: 0index: 1index: 2index: 3index: 4There is a lot more we can discuss about scope in JavaScript, but I feel like this is a solid introduction to what scope is, the different types of scope, and how to use some design patterns to take advantage of this.
In the next article, I would like to cover whatcontext andthis are in JavaScript, whatexplicit or hard bindings are, and what thenew keyword represents.
I hope this helped clarify what scope is, and if you have any questions or comments, do not hesitate to respond via the comments.
If you're interested in better learning what JavaScript is and how it works at its core, you can follow along onmy blog.

Wissam is a Senior JavaScript Developer / Consultant at Clevertech, a company that specializes in building MVPs for startups in an agile environment, and using cutting-edge technologies.. Visit his blog atDesigning for Scale. Find him on GitHubhere.