Javascript 101: Hoisting
tl;dr - “hoisting” is a default behaviour of Javscript, basically it means “variables and function declarations get hoisted to the top”.
The concept of hoisting can throw off some developers, as it isn’t too intuitive to begin with so lets look at an example. Consider the following code:
variable = 1;
console.log(variable);
var variable;
What is the expected output here? Funnily enough it will log out 1 even though it seems the variable was declared afterwards.
To begin understanding what is happening here we first need to understand execution contexts'.
When working on a project, our code is usualy spread out over many files, and in these files our code can be oganised into functions, classes and so on.
The place where code lives physically is referred to as its lexical environment. A codebase can have lots of lexical environments but the code that is running is managed by the execution context. Execution context
In Javascript there are two execution contexts to know, the Global Execution Context (GEC) and the Function Execution Context (FEC).
Each context gets its own creation and execution phase, as long as you remember that with the GEC you get access to the window global object, and you get the special this keyword to use.
During the FEC creation phase you also get access to a functions special arguments object.
// Global Context
window.something = "something";
funcA(1, "b");
function funcA(a, b) {
// Function Context
// The "arguments" object every function has access to, it's treated like an array...
console.log(arguments); // [0: 1, 1: "b"]
console.log(arguments[0]); // 1
console.log(arguments[1]); // 'b'
console.log(window.something); // "something", as a function context can access global contexts but not the other way around
funcB();
var a = 1;
function funcB() {
// Function Context inside a function context
var a = 2;
console.log(a);
}
console.log(a);
}
A far deeper and more concise explanation of the relationship between hoisting and the execution context can be found at freecodecamp.com(https://www.freecodecamp.org/news/javascript-execution-context-and-hoisting/). Variable Hoisting
During the creation phase memory is allocated to a variable and it is automatically given a value of undefined.
Afterwards during the execution phase is when code is actually executed.
This can lead to some confusion sometimes when trying to execute code that isn’t “ready” yet, or you are trying to call functions before they are available inside an execution context. This is the reason you sometimes get unexpected behaviours while using breakpoints in Javascript. Function Hoisting
Function Hoisting is more or less the same as variable hoisting, except functions also get their own execution context.
It’s generally good practice to hoist all variables and function declarations yourself to the top of the file to reduce any confusion as to when code will be executed. Declarations > Initialisations
Put simply, Javascript will automatically hoist functions you have declared as functions, but it balks when you try to call a function before it has been initialised, again this is probably better articulated with some code:
// Using our example above
funcA(); // 'yo' function funcA(){console.log('yo')}
This is fine, as even though we have declared what the function will do we actually haven’t tried to initialize it yet.
But if we do this:
funcA();
var funcA = function () {
console.log("yo");
};
We run into the following TypeError:
Uncaught TypeError: funcA is not a function
The reason this throws an error is due to what happens when functions are assigned as an expression like they are here - when this happens Javascript is only aware of expressions when the execution flow reaches them, as opposed to being aware of function declarations as soon as the code begins to execute. All thanks to hoisting. Do “let” and “const” get hoisted?
Going back to the first example above
variable = 1;
console.log(variable);
var variable;
Replacing the var keyword with either let or const will actually throw an error
// const
variable = 1;
console.log(variable); // Uncaught SyntaxError: Missing initializer in const declaration
const variable;
// let variable = 1; console.log(variable); // Uncaught ReferenceError: Cannot access 'variable' before initialization let variable;
The reasons for this are a little complicated, but basically to avoid issues with var and hoisting, the let and const keywords have been introduced by way of block level scoping (which is, out of scope, for this article).
In a tl;dr manner of speaking, let and const declarations are hoisted but are only initialised when their assignment is evaluated during execution. This is a fancy way of saying that you basically can’t access these variables until Javascript’s had a good ol’ look at what it is meant to be. For even more fun words, this is referred to as a temporal dead zone, a time span between when the variable is created and it’s actual initialisation.
Further notes
- You can add “use strict” at the top of files to avoid hoisting.
- Avoid bad habits, and hoist variables and functions yourself to aid readability.
- Most issues around with hoisting can be avoided by decent working practices.
Further reading
https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions
https://stackoverflow.com/questions/336859/var-functionname-function-vs-function-functionname