Variable hoisting is a behaviour in JavaScript where variable declarations are moved to the top of the scope (function scope or global scope) that the variable is defined within. The typical JavaScript variable can be created in two stages - declaration and initialisation.

The declaration stage is where the variable reference is created, without any value. At this stage, if we try to access the variable value, we will get undefined. The second initialisation stage is where the actual value is assigned to the variable.

function exampleScope() {  
  var foo; // Declaration
  foo = ‘Hello, world!’; // Initialisation

  var bar = ‘Hi there!’; // Declaration & Initialisation in one step
}

Hoisting is the behaviour of moving the declaration stage to the top of the scope, regardless of if the variable is declared and initialised in one step. This has always been a peculiar behaviour in JavaScript we just had to accept and work around. Fortunately, with the introduced of two new types of variables in ES2015, this behaviour is less of an issue.

Hoisting and var

Prior to ES2015, var was the only form of variable we could use. The var variable is scoped to the function it sits within, or the global scope if it isn’t within a local function. Even with the introduction of let and const in ES2015, the old var still exists in its typical form.

When a variable is created using var, regardless of how, the declaration of the variable is “hoisted” to the top of the scope. For example, consider the following function -

function exampleScope() {  
  // Other function stuff here
  var foo = ‘Hello, world!’;
}

The function above will actually be interpreted as the following -

function exampleScope() {  
  var foo;
  // Other function stuff here
  foo = ‘Hello, world!’;
}

At first glance, this may not seem to have a real effect on anything. However, it can lead to a very peculiar behaviour where variables can be used before they are actually created in our code.

Hoisted variables can be accessed before they are initialised

Typically, if we try to use a variable that has not been defined, we get a ReferenceError, which stops the rest of the script for executing.

function exampleScope() {  
  console.log(foo); // => ReferenceError: foo is not defined [Stop script execution]
  console.log(“This will never be logged”);
}

However, if we declare and initialise the variable after trying to use it, we simply get undefined, a non-blocking error.

function exampleScope() {  
  console.log(foo); // => undefined
  var foo = “Hello, world!”;
  console.log(foo); // => “Hello, world!”
  console.log(“This sentence will be logged”); // => “This sentence will be logged”
}

Because of hoisting, the above code is actually equivalent to us having written the following -

function exampleScope() {  
  var foo;
  console.log(foo); // => undefined
  foo = “Hello, world!”;
  console.log(foo); // => “Hello, world!”
  console.log(“This sentence will be logged”); // => “This sentence will be logged”
}

Hoisting and let

The let variable is a new form of variable introduced in ES2015. Instead of being scoped to the nearest function, let is scoped to the nearest block. This means that let variables defined within non-function blocks like if will only be available within those blocks.

The let variable, like var, can be created in the two stages - declaration and initialisation. However, the hoisting behaviour does not apply. If we repeat the example above using let instead, we will get a ReferenceError because foo does not exist at the point it is trying to be used.

function exampleScope() {  
  console.log(foo); // => ReferenceError: foo is not defined [Stop script execution]
  let foo = “Hello, world!”; 
  console.log(foo); 
  console.log(“This will never be logged”);
}

Hoisting and const

The const variable is another new form of variable (technically, a constant) introduced in ES2015. Like let, it is scoped to the block, rather than the function. Unlike let and var, const must be declared and initialised in one step. Additionally, once this step is complete, the value cannot be modified.

Because of this, hoisting does not apply to const either.

function exampleScope() {  
  console.log(foo); // => ReferenceError: foo is not defined [Stop script execution]
  const foo = “Hello, world!”; 
  console.log(foo); 
  console.log(“This will never be logged”);
}

Function Hoisting

In addition to variables, hoisting applies to functions created using the function declaration syntax.

Function declarations, as with the const variable, are defined and initialised in one step. However, functions are hoisted by moving the entire function, not just the declaration, to the top of the scope. This means that functions can always be used before they are created, as long as the usage is within the same scope.

function exampleScope() {  
  foo(); // => “Hello, world!”

  function foo() {
    console.log(“Hello, world!”)
  }
}

Functions referenced by variables, on the other hand, are subject to the hoisting rules for the variable used. For example, if we create a function and assign it to a var variable, the declaration will be hoisted to the top of the scope. However, because it is only declared and not initialised as a function, we will not be able to call the function before the initialisation stage.

function exampleScope() {  
  console.log(foo); // => undefined

  foo(); // => TypeError: foo is not a function [Stop script execution]

  var foo = function() {
    console.log(“Hello, world!”)
  }
}

What does this mean for ES2015?

When writing ES2015, the best practice is to always use const. If you need the variable to be updated, then use let. There should be almost no circumstance in which using var is the better option, and so hoisting should not be an issue.

Nonetheless, function hoisting is still present, so this is a behaviour we should still be conscious of.