Hmmm, participating in that JavaScript boot-camp course was easy: you followed the instructions and wrote code word for word. Just like that paint-by-the-numbers activity. They tell you about variable and function hoisting, function declarations and expressions. Moreover, once ES6 (ECMAScript® 2015) syntax gets involved, the arrow functions appear. Eventually, you even use all of those in your sample project and it works! But do you really grasp, what is the difference between all those and practical use cases for each one?

In fact, my eye-opening moment happened when I did a test assignment for a potential job. It came to me in the form of:

Uncaught ReferenceError: Cannot access 'myFunction' before initialization

How so?! What about all the fancy function hoisting story?

Well, seems it is time to dive into the depths of JS and figure this one out. Hopefully, we will not end up all frustrated and knee-deep into the topics of this and closures. For sure, there will be a better time for that.

Function declaration and function expression

You can define a function in multiple ways in JS. Each way results in a perfectly capable function, because in essence those are just different ways to create a Function object.
According to MDN:

Function Declaration

Defines a function through a function statement.

function myFunction (params){
    //body of the function

Function declaration is hoisted to the top of local (or global) scope.

Function Expression

Defines a function inside an expression with function keyword. Syntax:

const myFunction = function (params){
   //body of the function

Function expressions are not hoisted at all.

Function Hoisting

Turning to MDN again: JavaScript only hoists declarations, not initializations.
Indeed, this is the entire explanation on why function expressions are not hoisted. Let’s split up the above function expression to declaration and initialization parts:

//declaration part (notice the use of 'let' instead of 'const' here)
let myFunction;          
//initialization part
myFunction = function (params){  
   //body of the function

This results in a situation, where function is hoisted by name, but will not return anything if called before the initialization part.
A little practical demonstration to prove the point:

First, let’s attempt to call a function callEngineering() before it is defined through a function expression. Click the result tab in a Fiddle to see the output. It will give us a self-explanatory error: not defined, not hoisted, not available, end of story.

Next up, calling a function after it is declared, but before it is initialized. This one will result in a hoisted callEngineering variable name, but without any content. It is not even known to be a function yet.

Finally, if we call the function after it was both declared and initialized, it makes total sense and returns the result we expect.

For comparison, let’s define the callEngineering function through function statement and marvel the miraculous function hoisting in action.

ES6: but what about arrow functions?

ES6 introduced us to a lean and more readable way to define a function. Truth be told, I was overusing this one heavily. However, to my despair function hoisting doesn’t work with arrow functions.
All for one simple reason: In essence, arrow function is a syntactic sugar for function expression. So, as we figured above, it will not be hoisted.

Lesser-known: Function constructor

Another way to define a function is to use a Function constructor like so:

const myFunction = new Function('params', '//body of the function');

No hope for this one to be hoisted either. Same reason as above: it is not a function statement, but a function expression.

All in all, if you want to benefit from JavaScript function hoisting, keep an eye on the way you define your functions and structure your code accordingly.


Illustration by Shvets Anna from Pexels