Hoisting

Hoisting is a popularly known term in JavaScript but do you understand how it works? If you've been able to invoke a function on a line prior to the line it was declared, read on to understand why.

In this article, we'll explore how hoisting works in JavaScript. But before we proceed, you need to understand certain concepts.

  • In JavaScript, there are two stages during code execution: the compilation phase and the execution phase.

  • Calling an undefined variable results to a reference error

console.log(myVariable) // Uncaught ReferenceError: myVariable is not defined
  • An undeclared variable is assigned the type of undefined
console.log(typeof myVariable) // undefined

Now that you understand these points, let's find out what hoisting means.

Hoisting is the act of putting variable and function declarations into memory during the compile phase. It is a general way of thinking about how execution contexts (the creation and execution phases) work in JavaScript.

Let's look at some code snippets for better understanding.

Variable Declaration

The widely known way of declaring a variable is:

var a = 'Read more' // let or const

Declaration and initialization takes place.

Now, let's look at other scenarios where this is not the case.

// Initialize a. Declares it if it's not already declared. No hoisting occurs.
a = 'Read'
console.log(a + ' ' + b) // 'Read undefined'
// Declare and initialize b. The value of b is not hoisted.
var b = 'more'

Wouldn't this result to a reference error since the variable was called before it was declared? you may ask. Well, that is not the case because of hoisting. Only the declaration (var b), not the initialization (='more') is hoisted. This results to b being assigned a value of undefined.

The code is executed in this order:

// Initialize a. Declares it if it's not already declared. No hoisting occurs.
a = 'Read'
var b // Stores the variable b in memory
console.log(a + ' ' + b) // 'Read undefined'
b = 'more'

But, you can use a variable before it is declared.

a = 'Read'
b = 'more'
console.log(a + ' ' + b) // 'Read more'
// Declare b. b is hoisted
var b
JavaScript only hoists declarations, not initializations. The value of an initialized variable returns undefined once it is used.

However, in strict mode, variables cannot be used if they are not declared.

Function Scoped Variables

Note that hoisting is scoped. This means that if variables are defined within a function, it is made available only within that function scope. Using it outside the scope results to a reference error. It is made available to the global scope only if it is declared outside a function.

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

function getB() {
  var b
  console.log(b)
  b = 'more'
}

getB() // Undefined

To avoid this bug, ensure to declare and initialize variables before using them.

function getB() {
  var b = 'more'
  console.log(b)
}

getB() // more

Function Declarations

The general way of working with functions is to declare it before you invoke it. However, in very rare cases, you may mistakenly use a function before you declare it in your code. Hoisting makes it work and not throw an error.

The code snippet below shows the generally known way of declaring and invoking a function.

function getTheLastDigitFromUrl(url) {
  const digit = url && url.match(/([\d]+)/g)
  if (digit && digit.length > 0) {
    return digit[0]
  }
  return url
}

getTheLastDigitFromUrl('https://chiamakaikeanyi.dev/20200702') //20200702

Now, what happens when the function is invoked before declaration?

getTheLastDigitFromUrl('https://chiamakaikeanyi.dev/20200702') //20200702

function getTheLastDigitFromUrl(url) {
  const digit = url && url.match(/([\d]+)/g)
  if (digit && digit.length > 0) {
    return digit[0]
  }
  return url
}

Nothing special, it yields the same result as the first scenario when the function is declared before being called. This is due to hoisting. Function declarations are hoisted. So we can invoke a function before we declare it.

Function Expressions

Function expressions are not hoisted. If you invoke a function expression before assigning a function to it, it results to a type error.

getTheLastDigitFromUrl('https://chiamakaikeanyi.dev/20200702')
// Uncaught TypeError: getTheLastDigitFromUrl is not a function

var getTheLastDigitFromUrl = function(url) {
  const digit = url && url.match(/([\d]+)/g)
  if (digit && digit.length > 0) {
    return digit[0]
  }
  return url
}

getTheLastDigitFromUrl is a variable initialized with undefined, so, invoking it as a function results to a type error.

Hoisting Variables vs Functions

Function declarations are hoisted before variable assignments. Let's execute the code below:

console.log(typeof getB) // Output: function

var getB = 'Read'

function getB() {
  var b = 'more'
  console.log(b)
}

Same occurs for variable declarations:

console.log(typeof getB) // Output: function

var getB

function getB() {
  var b = 'more'
  console.log(b)
}

Notice that although the variable was declared before the function, the type logged to the console is function.

Strict Mode

In strict mode, variables cannot be used if they are not declared. Doing this throws an error.

'use strict'

console.log(a) // Uncaught ReferenceError: a is not defined
a = 'Read more'

The let and const Keywords

Variables and constants declared with let or const are hoisted but unlike var, they are not initialized with undefined. Using them before initialization results to a reference error rather than undefined. Variables declared with let is block scoped ({}) NOT function scope.

console.log(a) // Uncaught ReferenceError: b is not defined
let a = 'Read more'

However, when let is moved to the top, it is hoisted. This is an antipattern though.

let a
console.log(a)
a = 'Read more'

Using const,

a = 'Read'
console.log(a + ' ' + b) // Uncaught ReferenceError: Cannot access 'b' before initialization
// Declare b. b is not hoisted
const b = 'more'

Note that you cannot declare the const variable without initialization. Attempting to do so results to a syntax error

a = 'Read';
console.log(a + " " + b);
// Declare b. b is not hoisted
const b
b = 'more'; // Uncaught SyntaxError: Missing initializer in const declaration

Points to Note

  • JavaScript only hoist declarations. Initializations are not hoisted.

  • Always declare variables at the top of every scope to avoid bugs.

  • Function declarations are hoisted while function expressions are not.

  • Variables and constants declared with let or const are hoisted but unlike var, they are not initialized with undefined. Using them before initialization results to a reference error rather than undefined.

  • Prefer let and const over var.

Conclusion

Having gone through and understood hoisting as a concept in JavaScript, it is pertinent to note that we should form a habit of declaring and initializing JavaScript variables before they are used. In ES 5, using the strict mode is encouraged to expose the loop holes.

Article Tag  JavaScript

Share this article:

Stay Updated

    More Articles


    I write about accessibility, performance, JavaScript and workflow tooling. If my articles have helped or inspired you in your development journey, or you want me to write more, consider supporting me.