Understanding Scope
In computer science, scope is the region of a computer program where the binding or association of a name to an entity, such as a variable or function, is valid. JavaScript has the following two distinct types of scope:
Function scope
Block scope
Until ES6, function scope was the only form of scope in JavaScript; all variable and function declarations followed function scope rules. Block scope was introduced in ES6 and is used only by the variables declared with the new variable declaration keywords let
and const
. These keywords are discussed in detail in the Declaring Variables section.
Function Scope
Function scope in JavaScript is created inside functions. When a function is declared, a new scope block is created inside the body of that function. Variables that are declared inside the new function scope cannot be accessed from the parent scope; however, the function scope has access to variables in the parent scope.
To create a variable with function scope, we must declare the variable with the var
keyword. For example:
var example = 5;
The following snippet provides an example of function scope:
var example = 5; function test() { var testVariable = 10; console.log( example ); // Expect output: 5 console.log( testVariable ); // Expect output: 10 } test(); console.log( testVariable ); // Expect reference error
Snippet 1.1: Function Scope
Parent scope is simply the scope of the section of code that the function was defined in. This is usually the global scope; however, in some cases, it may be useful to define a function inside a function. In that case, the nested function's parent scope would be the function in which it is defined. In the preceding snippet, the function scope is the scope that was created inside the function test. The parent scope is the global scope, that is, where the function is defined.
Note
Parent scope is the block of code, which the function is defined in. It is not the block of code in which the function is called.
Function Scope Hoisting
When a variable is created with function scope, it's declaration automatically gets hoisted to the top of the scope. Hoisting means that the interpreter moves the instantiation of an entity to the top of the scope it was declared in, regardless of where in the scope block it is defined. Functions and variables declared using var
are hoisted in JavaScript; that is, a function or a variable can be used before it has been declared. The following code demonstrates this, as follows:
example = 5; // Assign value console.log( example ); // Expect output: 5 var example; // Declare variable
Snippet 1.2: Function Scope Hoisting
Note
Since a hoisted variable that's been declared with var
can be used before it is declared, we have to be careful to not use that variable before it has been assigned a value. If a variable is accessed before it has been assigned a value, it will return the value as undefined
, which can cause problems, especially if variables are used in the global scope.
Block Scope
A new block scope in JavaScript is created with curly braces ({}
). A pair of curly braces can be placed anywhere in the code to define a new scope block. If statements, loops, functions, and any other curly brace pairs will have their own block scope. This includes floating curly brace pairs not associated with a keyword (if, for, etc). The code in the following snippet is an example of the block scope rules:
// Top level scope function scopeExample() { // Scope block 1 for ( let i = 0; i < 10; i++ ){ /* Scope block 2 */ } if ( true ) { /* Scope block 3 */ } else { /* Scope block 4 */ } // Braces without keywords create scope blocks { /* Scope block 5 */ } // Scope block 1 } // Top level scope
Snippet 1.3: Block Scope
Variables declared with the keywords let
and const
have block scope. When a variable is declared with block scope, it does NOT have the same variable hoisting as variables that are created in function scope. Block scoped variables are not hoisted to the top of the scope and therefore cannot be accessed until they are declared. This means that variables that are created with block scope are subject to the Temporal Dead Zone (TDZ). The TDZ is the period between when a scope is entered and when a variable is declared. It ends when the variable is declared rather than assigned. The following example demonstrates the TDZ:
// console.log( example ); // Would throw ReferenceError let example; console.log( example ); // Expected output: undefined example = 5; console.log( example ); // Expected output: 5
Snippet 1.4: Temporal Dead Zone
Note
If a variable is accessed inside the Temporal Dead Zone, then a runtime error will be thrown. This is important because it allows our code to be built more robustly with fewer semantic errors arising from variable declaration.
To get a better understanding of scope blocks, refer to the following table:
In summary, scope provides us with a way to separate variables and restrict access between blocks of code. Variable identifier names can be reused between blocks of scope. All new scope blocks that are created can access the parent scope, or the scope in which they were created or defined. JavaScript has two types of scope. A new function scope is created for each function defined. Variables can be added to function scope with the var
keyword, and these variables are hoisted to the top of the scope. Block scope is a new ES6 feature. A new block scope is created for each set of curly braces. Variables are added to block scope with the let
and const
keywords. The variables that are added are not hoisted and are subject to the TDZ.
Exercise 1: Implementing Block Scope
To implement block scope principles with variables, perform the following steps:
Create a function called
fn1
as shown (function fn1())
.Log the string as
scope 1.
Create a variable called
scope
with the value of 5.Log the value of the variable called
scope
.Create a new block scope inside of the function with curly braces (
{}
).Inside the new scope block, log the string called
scope 2.
Create a new variable called
scope
, inside the scope block and assign the valuedifferent scope
.Log the value variable
scope
inside our block scope (scope 2).Outside of the block scope defined in step 5 (scope 2), create a new block scope (use curly braces).
Log the string called
scope 3.
Create a variable inside the scope block (scope 3) with the same name as the variables (call it
scope
) and assign it the valuea third scope
.Log the new variable's value.
Call
fn1
and observe its output
Code
index.js:
function fn1(){ console.log('Scope 1'); let scope = 5; console.log(scope); { console.log('Scope 2'); let scope = 'different scope'; console.log(scope); } { console.log('Scope 3'); let scope = 'a third scope'; console.log(scope); } } fn1();
Snippet 1.5: Block implementation output
Outcome
You have successfully implemented block scope in JavaScript.
In this section, we covered the two types of JavaScript scope, function and block scope, and the differences between them. We demonstrated how a new instance of function scope was created inside each function and how block scope was created inside each set of curly braces. We discussed the variable declaration keywords for each type of scope, var
for function scope and let/const
for block scope. Finally, we covered the basics of hoisting with both function and block scope.