Understanding JS Variables – Scopes & Hoisting

This post is work-in-progress learning-note and still in active development and updated regularly.

JavaScript variables and variable declaration are described in Declaring JS Variables – var, let & const post separately. In the posts, variable scope & hoisting were discussed briefly.  In this post we try to look scope & hoisting in more detail. Understanding function/variable scope and hoisting is essential to better understand & use JS language. The concept of JS scope & hoist is confusing to most JS developers including beginners.

Variable Scope

The term <strong>scope</strong> often confusing to most new JS developers. Scope is the accessibility of  variables, functions and objects from different parts in a code block. In other words, scope refers to the current context of variable in the code block.  There are two types of scopes: Global & Local.

1. Global Scope

When a variable is declared outside of any function, it is called a global variable and it is said to be defined in the global scope. A global variable has global scope and is available to any other code in the current document. In a browser, the global scope is the window object.

//'course' is global variable 
var course = 'I am science - a global variable'; 

//'course' is function local variable 
function test() { 
  var course = 'I am Science too, available local'; 
  console.log(course); //I am local course
} 
test(); //outputs local
console.log(course); //I am GLOBAL course

//Output
I am Science too, available local
I am science - a global variable

Note: if a variable is declared & forget to use the var keyword, that variable is automatically made global.

2. Local Scope

When a variable is declared within a function, it is called a local variable and local variables have local scope.  A local variable is available only within that function.

// code here can’t use carName

function myCar() {
    var carName = "Volvo";
    // local scope: code here can use carName
    cosole.log(carName);
}
//invoke function myCar
myCar();

//OUTPUT
Volvo

Locally scoped variables are not visible (accessible) outside of a function.

var myFunction = function () {
  var name = 'Rob';
  console.log(name); // Rob
};

// Uncaught ReferenceError: name is not defined
console.log(name);

// Output
undefined

The variable name is scoped locally, it is not accessible outside of the function (as global scoped variable) and therefore outputs as undefined.

In the above example the variable name was created with function myFunction and scoped locally. The variable identifier name can be accessed and used anywhere within the myFunction.  However, if variable name accessed & used outside of myFunction, it will output  an Uncaught ReferenceError

Variable Hoisting

To quote Dmitri Pavlutin’s article: “Hoisting is the process of virtually moving the variable or function definition to the beginning of the scope, usually for variable statement var and function declaration function fun() {...}.”

JS variables declared with var keyword can be call first and define, declare later. JS engine does not through any error but returns a value of undefined. This concept is known as ‘hoisting‘. Variables that are hoisted return a value of undefined (MDN). Lets explain this hoisting concept in the following example:

// invoking first undeclared variable (1)
console.log(car);

// Declaration of variable later (2)
var car = 'Toyota';

//OUTPUT
undefined

In the above example, we call the value of variable identifier car first (in step 1) before it was declared and assigned a value of 'Toyota' (step 2) and we got expected output undefined.

1. Only Declarations are Hoisted and NOT Initialization

An attempt to access an undeclared variable results in a ReferenceError exception error (MDN Evaluating Variables).  Lets examine this concept using our above example:

// invoking first undeclared variable (1)
console.log(car);

// Initialize variable later (2)
car = 'Toyota';

//OUTPUT
ReferenceError: car is not defined

// JS Engine Interpretation
var car;  // Declare & initialize 
console.log(car); // ReferenceError: car is not defined
car= 'Toyota'; // assigned value to var identifier

In the example above, we call variable identifier car first (in step 1) and then we only initialize variable car to 'Toyota' without using var keyword. Variables assigned without keyword var are not declared (MDN) but only assigned and therefore it stop its execution and throw ReferenceError.

When JS engine interpreted the code, the identifier car was read and moved to the top as undeclared variable and initialized it. Next, when it executed console.log(car); – it stopped execution and throw ReferenceError.  In MDN, it is referred as variable being in a “temporal dead zone” from the start of the block until the declaration is processed.

To quite from MDN Variable hoisting: “Because of hoisting, all var statements in a function should be placed as near to the top of the function as possible. This best practice increases the clarity of the code.

Function Scope

When a variable is declared in a function, this variable can only be accessed within the function. It can’t get out of it like as global variable. In JS, variables declared (using var keyword) are scoped at function level.

1. General Syntax: Function Scope
//General syntax
// Scope A
var myFunction = function () {
  // Scope B
  var myOtherFunction = function () {
    // Scope C
  };
}
2. Function with Function level scope

Lets declare a simple function myGreeting () and declare var hello with a assigned string value 'Good Morning, Guy!'. In the step (2) we call the function myGreeting().

// Initialize function (1)
function myGreeting () {
  var hello = 'Good Morning, Guys!';
  console.log(hello);
}

// Invoke (call) function (2)
myGreeting() // (3) 'Good Morning, Guys!'
console.log(hello) // (4) Error, hello is not defined

//OUTPUT (5)
Good Morning, Guys! // (3)
ReferenceError: hello is not defined //(4)

In the above example, variable hello is declared and assigned a value within function myGreeting () and therefore it has function scope, meaning it is available within myGreeting function only (3). When value of the var hello was called from outside of the myGreeting(), we got a ReferenceError. An explanation how JS engine executed this code is described under variable hoisting section (see above).

3. Function defined in Global Scope & Inside Another Function

As stated in MDN function scope, a function can access variables and functions defined inside the scope in which it is defined. On the other hand, a function defined in the global scope can access all variables defined in the global scope. A function defined inside another function can also access all variables defined in its parent function and any other variable to which the parent function has access. Looking at the example from MDN below:

// The following variables are defined in the global scope (1)
var num1 = 20,
    num2 = 3,
    name = 'Chamahk';

// This function is defined in the global scope (2)
function multiply() {
  return num1 * num2;
}
// Invoke function multiply
multiply(); // Returns 60 (3)

// OUTPUT
60

// A NESTED FUNCTION EXAMPLE (4)
function getScore() {
  var num1 = 2,
      num2 = 3;

 //Declare & define a nested function
  function add() { // (5)
    return name + ' scored ' + (num1 + num2);
  }
  // call nested function add
  return add(); // (6)
}

// Invoke function getScore
getScore(); // Returns "Chamahk scored 5" (7)

//OUTPUT  [ getScore() - 7]  (8)
 "Chamank scored 5"

In the above MDN example, three global variables (on the top & outside of function) were declared and defined (1). Next (step 2), function multiply () was declared using two global variables: num1, num2 (from step 1). Since these variables are declared as global variable, they are accessible in function multiply() as shown in the output in multiply() call (step 3).

Now lets look at the function scope in nested function (step 4). Here we declare and define function getScore() using the two global variable num1 & num2 (step 1) and assign some values to them. Inside function getScore() we create a nested function add() (step 5) and define with  a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return" target="_blank" rel="noopener">return</a> statement using global variable (1) name, and num1 and num2 variable  with re-assigned value inside getScore() (step 4). The add() is called inside getScore() function and the value is assigned to return statement.

Finally, when the parent function getScore() is called (step 7), it also calls the inside function add() and returns the expected output.

4. Nested Function or Lexical Scope (Function Expression)

Lets look at another Nested function example from Todd Motto. When there are nested functions (eg. outer function, inner function), the inner function can access the variables declared in outer function.

Lexical scope (also referred as Nested Function) is the ability of an inner function to access the scope of an outer function. The following example from Todd motto.

//Initialization of function expression
var myFunction = function () {
  var name = 'Rob'; //local scope variable
  
  //initialization of nested function
  var myOtherFunction = function () {
    console.log('My name is ' + name);
  };
  //Invoke var & function
  console.log(name);
  myOtherFunction(); // call function
};

// OUTPUT
Todd  // var name
My name is Rob  // myOtherFunction

Function Hoisting

Similar variable hoisting, JS functions can be called (invoked) before their declaration, a phenomenon known as function hoisting. This allows JS engine to store function declarations into memory before it executes any code segment and thus making possible to call (invoke) a function before its declaration.

// Declare function carName (1)
function carName(name) {
  console.log("I drive " + name);
}

// Invoke function carName (2)
carName("Toyota");

//OUTPUT (3)
I drive Toyota

In the example above, a function with carName(name) was declare in (1) with single parameter name. The function has single line of code to print a simple text string 'I drive ' with passed on argument name during function call. When the function carName("Toyota") was  called, it printed expected output (3).

Now lets examine (below) what happens when we call the function carName("Toyota") first (1) and declare the function CarName(name) after in step (2). We get the same output as above.

// Invoke function carName (1)
carName("Toyota");

// Declare function carName (2)
function carName(name) {
  console.log("I drive " + name);
}

//OUTPUT (3)
I drive Toyota

Lets explain what is going on here. When JS engine recognized function declaration with function keyword (1), it then initialized and hoisted it at the top and making it available for use later during its execution phase (2). On the other hand, function expression are not hoisted.

1.  Function Expression are NOT Hoisted

JS function expression are not hoisted. That means, we can’t call (invoke) function before it is defined. Let’s examine this in following two examples:

1.1. Regular Function Expression

In the following example, the function expression carName is defined & called in normal way (define first and call after). We get expected output (3) without any error.

//Create function expression (1)
var carName = function(name){
  console.log("I drive " + name);
}

//Call function carName (2)
carName("Toyota");

//OUTPUT (3)
I drive Toyota
1.2. Function Expression Call Before It’s Define

In the example below, the same function CarName(name) is called first (step 1). Then carName variable was to function(name) as a function expression. When the function was executed we got an TypeError thrown.

//Call function carName (1)
carName("Toyota");

//Create function expression (2)
var carName = function(name){
  console.log("I drive  " + name);
}

//OUTPUT (3)
TypeError: carName is not a function

Lets examine how the JS engine interpreted the above code. During the memory creation phase it encounters var keyword and expecting variable declaration it hoist at the top and assign a value: undefined. Because carName variable has not been declared and variable initiation are NOT hoisted.

Although CarName variable is initialized with ( = function(name){console.log("I drive " + name);) } ) it was not hoisted. Therefore, when CarName("Toyota") function was called (1), it has a undefined (hoisted) value but it is not a function and execution failed (see example 1.1).

Tip: Function hoisting works only when function is declared but does not work in function expression.

Additional information on function declaration & function expression is described in a separate post: JavaScript Functions – Declaration and Expression.

Further Reading:

While preparing this post, I have referred the following references extensively. Please to refer original posts for more detailed information.