Hoisting
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
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!