Hoisting
hoisting is the processing of declarations before any code is executed.
What's a declaration though?
- a declaration is a way of telling the interpreter (or compiler) that a name or identifier exists
- we learned a few ways of declaring names in JavaScript (the 1st is easy, the 2nd, a bit tricky)… what were they? →
- variable declarations (using
const
,let
, andvar
) - function declarations (using
function f(x) {}
)
- variable declarations (using
Hoisting basically brings declarations to the top of the current scope. What does that mean for us? →
- some declarations do not have to occur before they are used!
- we already saw this with functions declarations...
Hoisting and Functions
So… we basically know what happens here. What's the output of the following code examples? →
f();
function f() {
console.log("TO THE TOP, PLZ!")
}
output: TO THE TOP, PLZ!
f();
var f = function() {
console.log("TO THE TOP, PLZ!")
}
output: TypeError: undefined is not a function
f();
output: ReferenceError: f is not defined
Hoisting and Functions SOME MORE!
What's the output of this code? →
function outer() {
inner();
function inner() {
console.log('hello');
}
}
outer();
hello
is printed out. What happens if we add in a call to inner at the end, outside of the function
?
// same as above (function outer() ... )
// but add the line below at the very end
inner();
ReferenceError
- function declarations are hoisted to the top of their current scope (not to the top of the global scope)
Great! But What About var
Declarations?
(We'll see why var f = function ...
behaves the we way it does)
var
Examples
Treating each code example as a completely separate program, what is the output of the following lines of code?→
console.log(x);
// variable not yet declared (easy)
ReferenceError: x is not defined
var x;
console.log(x);
// x is declared before ... works obvs!
undefined
// so... what do we get here?
console.log(x);
var x;
undefined
Note that the last example would be an error if using let
(or const
)
var
Declarations are Hoisted
console.log(x);
console.log(y);
var x;
var y;
In the above example:
- the variable declarations are taken from the regular top-to-bottom flow
- … and they are treated as if they were moved to the beginning of their enclosing scope
- consequently, this prints out
undefined
twice rather thanReferenceError
How About Initializing a Variable Along with var
?
Let's start simple. What's the output of this code?
var x = 5;
console.log(x);
// no surprise here!
5
But how about this? →
console.log(x);
var x = 5;
// oof. what!?
undefined
var
and Initialization
var
declarations are hoisted- but the initialization is executed in the location of the program where the initialization statement is actually placed
- soooo… that means:
console.log(x);
var x = 5;
- is executed as:
var x;
console.log(x);
x = 5;
Another Note on var
and Hoisting
This probably doesn't matter since, we all know that you should never declare a variable without var, but:
- implicit variable declarations are not hoisted!
- the following gives us a
ReferenceError
// (all we did here was take out var!)
console.log(x); // oops ... ReferenceError
x = 5;
What About let
and const
?
As we saw previously, let
and const
have a Temporal Dead Zone →
- a variable declared with
let
andconst
cannot be accessed…- between the time that the containing scope is entered
- and the actual
let
orvar
declaration
- however… an identifier is actually created for it at the beginning of the scope! you just can't use it yet!
- (so it's sort of hoisted; there seems to be some debate on the terminology for this)
- consequently, this code gives an error:
console.log(x); let x = 5;
Hoisting Summary
This is all you need to know about hoisting:
let
andconst
declared variables cannot be accessed until their declaration (this is actually sane)var
declarations and function declarations are brought to the beginning of their enclosing scope- all function declarations are hoisted
var
declarations are hoisted, but the assignment part occurs where the original statement was locatedvar
declarations that haven't been assigned a value yet are initialized withundefined
(just likelet
)
- implicit variable declarations (no
const
,let
, orvar
) are not hoisted (but you always uselet
,const
orvar
, so not relevant, right?)
Hoisting Example 1
What's the output of the following code? →
var num = 1000;
f();
function f(){
console.log(num)
var num = 5;
};
undefined
- the global
num
is not used - instead,
num
within the function is hoisted to the top of its enclosing scope, the function,f
- but note the initialization is executed in the place where it occurs… consequently,
undefined
Hoisting Example 2
What's the output of the following code? →
console.log(f);
var f = function(x) {
console.log("hello " + x);
}
- yup, still
undefined
- the declaration is hoisted - … but the initialization to a value is not
// it's executed as if it were
var f;
console.log(f);
f = function(x) {
console.log("hello " + x);
}
Hoisting Example 3
What's the output of this code? →
var inner = 1000;
function outer () {
inner = 5;
function inner() {}
}
outer();
console.log(inner);
- the output is
1000
- the function declaration of
inner
is hoisted to the top of the enclosing scope - which renders the first line of the function,
inner = 5
, a reassignment of the localinner
, not the global
Hoisting Example 4
What's the output of this code? →
console.log(f);
const f = function() {
console.log('I am function!');
}
ReferenceError
(temporal dead zone for const and let; used before declared)
Back to an Earlier Mystery
And that's why the following gives us undefined is not a function
→
f();
var f = function(x) {
console.log("hello " + x);
}
- we know that the declaration of
f
is hoisted - but it has no value at the point that it is invoked/called (it's
undefined
) - consequently, the program is attempting to use
undefined
as a function
Whew! That Seemed Unnecessarily Complicated.
Why even? ಠ_ಠ
No Seriously…
Hoisting. Why? →
- generally a top-down approach is taken to programming
- so it may make sense for the "main" part of the program to go on top, calling functions elsewhere
- those functions are likely to be declared below main
- so it's more natural… ¯\_(ツ)_/¯ (maybe)
- as for
var
, I don't know if I can excuse that!
- according to this SO article….
- it may possibly just be due to the interpreter implementation: scan source for variable and function declarations first, then execute code next
Easy, Right?
A lot of the design decisions in JavaScript seemed to be made for ease of use
Though, in certain cases these features actually make things more complex (weak typing, hoisting, etc.)Hoisting Can Lead to Tricky Situations
Like the one we saw before:
var num = 1000;
f();
function f(){
console.log(num)
var num = 5;
};
How can we get around this ambiguity? →
Always declare your variables at the beginning of your function!
Aaaaand… possibly, avoid using var
; use let
and const
instead!